@defra/interactive-map 0.0.37-alpha → 0.0.39-alpha
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/assets/templates/draw-tools.njk +4 -3
- package/dist/css/index.css +1 -1
- package/dist/esm/im-core.js +1 -1
- package/dist/esm/im-shell.js +1 -1
- package/dist/umd/im-core.js +1 -1
- package/dist/umd/index.js +1 -1
- package/docs/api.md +32 -0
- package/docs/examples/draw-tools.mdx +0 -4
- package/docs/plugins/draw.md +482 -0
- package/docs/plugins.md +2 -6
- package/govuk-prototype-kit.config.json +3 -2
- package/package.json +2 -4
- package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
- package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
- package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
- package/plugins/beta/draw-ol/dist/esm/im-draw-ol-plugin.js +1 -1
- package/plugins/beta/map-styles/dist/css/index.css +1 -97
- package/plugins/datasets/dist/esm/im-datasets-plugin.js +1 -1
- package/plugins/datasets/dist/umd/im-datasets-plugin.js +1 -1
- package/plugins/datasets/src/registry/dataset.js +3 -1
- package/plugins/datasets/src/registry/datasetRegistry.js +30 -24
- package/plugins/datasets/src/registry/datasetRegistry.test.js +25 -18
- package/plugins/draw/dist/esm/im-draw-ml-adapter.js +1 -0
- package/plugins/draw/dist/esm/im-draw-ol-adapter.js +1 -0
- package/plugins/draw/dist/esm/im-draw-plugin.js +1 -0
- package/plugins/draw/dist/esm/index.js +1 -0
- package/plugins/draw/dist/umd/im-draw-ml-adapter.js +1 -0
- package/plugins/draw/dist/umd/im-draw-ol-adapter.js +2 -0
- package/plugins/draw/dist/umd/im-draw-ol-adapter.js.LICENSE.txt +1 -0
- package/plugins/draw/dist/umd/im-draw-plugin.js +2 -0
- package/plugins/draw/dist/umd/im-draw-plugin.js.LICENSE.txt +1 -0
- package/plugins/draw/dist/umd/index.js +2 -0
- package/plugins/draw/dist/umd/index.js.LICENSE.txt +1 -0
- package/plugins/draw/src/DrawInit.jsx +89 -0
- package/plugins/draw/src/DrawInit.test.jsx +184 -0
- package/plugins/draw/src/adapterEvents.js +127 -0
- package/plugins/draw/src/adapterEvents.test.js +20 -0
- package/plugins/draw/src/adapters/adapterContract.test.js +48 -0
- package/plugins/draw/src/adapters/loadDrawAdapter.js +14 -0
- package/plugins/draw/src/adapters/loadDrawAdapter.test.js +53 -0
- package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.js +367 -0
- package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.test.js +799 -0
- package/plugins/draw/src/adapters/maplibre/defaults.js +1 -0
- package/plugins/draw/src/adapters/maplibre/drawEvents.js +35 -0
- package/plugins/draw/src/adapters/maplibre/mapboxDraw.js +126 -0
- package/plugins/draw/src/adapters/maplibre/mapboxDraw.test.js +268 -0
- package/plugins/draw/src/adapters/maplibre/mapboxSnap.js +39 -0
- package/plugins/draw/src/adapters/maplibre/mapboxSnap.test.js +91 -0
- package/plugins/draw/src/adapters/maplibre/modes/createDrawMode.js +58 -0
- package/plugins/draw/src/adapters/maplibre/modes/createDrawMode.test.js +53 -0
- package/plugins/draw/src/adapters/maplibre/modes/disabledMode.js +23 -0
- package/plugins/draw/src/adapters/maplibre/modes/disabledMode.test.js +21 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawLineMode.js +15 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawLineMode.test.js +37 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/__helpers__/harness.js +115 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/clickHandlers.js +193 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/clickHandlers.test.js +241 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/keyboardHandlers.js +78 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/keyboardHandlers.test.js +75 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.js +78 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.test.js +48 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/pointerHandlers.js +103 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/pointerHandlers.test.js +127 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/renderHelpers.js +59 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/renderHelpers.test.js +34 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/undoHandlers.js +193 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/undoHandlers.test.js +195 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawPolygonMode.js +16 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawPolygonMode.test.js +27 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/__helpers__/harness.js +107 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/geometryHelpers.js +139 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/geometryHelpers.test.js +46 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/helpers.js +2 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/helpers.test.js +17 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.js +151 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.test.js +123 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/pointerHandlers.js +161 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/pointerHandlers.test.js +98 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.js +133 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.test.js +105 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.js +166 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.test.js +133 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.js +210 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.test.js +167 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexQueries.js +121 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexQueries.test.js +73 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.js +249 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.test.js +153 -0
- package/plugins/draw/src/adapters/maplibre/snap/constants.js +2 -0
- package/plugins/draw/src/adapters/maplibre/snap/mapHandlers.js +39 -0
- package/plugins/draw/src/adapters/maplibre/snap/mapHandlers.test.js +106 -0
- package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.js +148 -0
- package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.test.js +238 -0
- package/plugins/draw/src/adapters/maplibre/snap/snapInstance.js +115 -0
- package/plugins/draw/src/adapters/maplibre/snap/snapInstance.test.js +151 -0
- package/plugins/draw/src/adapters/maplibre/snap/sourceData.js +34 -0
- package/plugins/draw/src/adapters/maplibre/snap/sourceData.test.js +62 -0
- package/plugins/draw/src/adapters/maplibre/styles.js +207 -0
- package/plugins/draw/src/adapters/maplibre/styles.test.js +155 -0
- package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.js +202 -0
- package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.test.js +253 -0
- package/plugins/draw/src/adapters/maplibre/utils/touchClickWorkaround.js +57 -0
- package/plugins/draw/src/adapters/maplibre/utils/touchClickWorkaround.test.js +106 -0
- package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.js +117 -0
- package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.test.js +159 -0
- package/plugins/draw/src/adapters/openlayers/__helpers__/harness.js +90 -0
- package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.js +178 -0
- package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.test.js +153 -0
- package/plugins/draw/src/adapters/openlayers/core/featureStore.js +69 -0
- package/plugins/draw/src/adapters/openlayers/core/featureStore.test.js +58 -0
- package/plugins/draw/src/adapters/openlayers/core/internalEvents.js +8 -0
- package/plugins/draw/src/adapters/openlayers/core/styles.js +155 -0
- package/plugins/draw/src/adapters/openlayers/core/styles.test.js +143 -0
- package/plugins/draw/src/adapters/openlayers/defaults.js +1 -0
- package/plugins/draw/src/adapters/openlayers/draw/DrawMode.js +272 -0
- package/plugins/draw/src/adapters/openlayers/draw/DrawMode.test.js +431 -0
- package/plugins/draw/src/adapters/openlayers/draw/drawInput.js +139 -0
- package/plugins/draw/src/adapters/openlayers/draw/drawInput.test.js +140 -0
- package/plugins/draw/src/adapters/openlayers/draw/vertexPlacement.js +144 -0
- package/plugins/draw/src/adapters/openlayers/draw/vertexPlacement.test.js +196 -0
- package/plugins/draw/src/adapters/openlayers/edit/EditMode.js +385 -0
- package/plugins/draw/src/adapters/openlayers/edit/EditMode.test.js +303 -0
- package/plugins/draw/src/adapters/openlayers/edit/activeVertexLayer.js +55 -0
- package/plugins/draw/src/adapters/openlayers/edit/activeVertexLayer.test.js +53 -0
- package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.js +157 -0
- package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.test.js +195 -0
- package/plugins/draw/src/adapters/openlayers/edit/midpointLayer.js +57 -0
- package/plugins/draw/src/adapters/openlayers/edit/midpointLayer.test.js +42 -0
- package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.js +83 -0
- package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.test.js +80 -0
- package/plugins/draw/src/adapters/openlayers/edit/nudge.js +118 -0
- package/plugins/draw/src/adapters/openlayers/edit/nudge.test.js +165 -0
- package/plugins/draw/src/adapters/openlayers/edit/pointerHandlers.js +79 -0
- package/plugins/draw/src/adapters/openlayers/edit/pointerHandlers.test.js +78 -0
- package/plugins/draw/src/adapters/openlayers/edit/selectionState.js +116 -0
- package/plugins/draw/src/adapters/openlayers/edit/selectionState.test.js +109 -0
- package/plugins/draw/src/adapters/openlayers/edit/touchHandler.js +184 -0
- package/plugins/draw/src/adapters/openlayers/edit/touchHandler.test.js +133 -0
- package/plugins/draw/src/adapters/openlayers/edit/undoOps.js +94 -0
- package/plugins/draw/src/adapters/openlayers/edit/undoOps.test.js +64 -0
- package/plugins/draw/src/adapters/openlayers/edit/vertexHitTest.js +74 -0
- package/plugins/draw/src/adapters/openlayers/edit/vertexHitTest.test.js +28 -0
- package/plugins/draw/src/adapters/openlayers/edit/vertexLayer.js +49 -0
- package/plugins/draw/src/adapters/openlayers/edit/vertexLayer.test.js +42 -0
- package/plugins/draw/src/adapters/openlayers/edit/vertexOps.js +100 -0
- package/plugins/draw/src/adapters/openlayers/edit/vertexOps.test.js +80 -0
- package/plugins/draw/src/adapters/openlayers/olDraw.js +40 -0
- package/plugins/draw/src/adapters/openlayers/olDraw.test.js +70 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapEngine.js +220 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapEngine.test.js +232 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapGeometry.js +198 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapGeometry.test.js +136 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapIndicator.js +81 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapIndicator.test.js +72 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.js +57 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.test.js +68 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapManager.js +99 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapManager.test.js +101 -0
- package/plugins/draw/src/adapters/openlayers/utils/geometryHelpers.js +103 -0
- package/plugins/draw/src/adapters/openlayers/utils/geometryHelpers.test.js +76 -0
- package/plugins/draw/src/adapters/openlayers/utils/olCoords.js +35 -0
- package/plugins/draw/src/adapters/openlayers/utils/olCoords.test.js +22 -0
- package/plugins/draw/src/adapters/openlayers/utils/sketchHelpers.js +22 -0
- package/plugins/draw/src/adapters/openlayers/utils/sketchHelpers.test.js +18 -0
- package/plugins/draw/src/adapters/openlayers/utils/touchTarget.js +8 -0
- package/plugins/draw/src/api/addFeature.js +22 -0
- package/plugins/draw/src/api/addFeature.test.js +41 -0
- package/plugins/draw/src/api/deleteFeature.js +11 -0
- package/plugins/draw/src/api/deleteFeature.test.js +24 -0
- package/plugins/draw/src/api/editFeature.js +54 -0
- package/plugins/draw/src/api/editFeature.test.js +134 -0
- package/plugins/draw/src/api/merge.js +30 -0
- package/plugins/draw/src/api/merge.test.js +57 -0
- package/plugins/draw/src/api/newLine.js +41 -0
- package/plugins/draw/src/api/newLine.test.js +93 -0
- package/plugins/draw/src/api/newPolygon.js +41 -0
- package/plugins/draw/src/api/newPolygon.test.js +93 -0
- package/plugins/draw/src/api/split.js +117 -0
- package/plugins/draw/src/api/split.test.js +204 -0
- package/plugins/draw/src/defaults.js +52 -0
- package/plugins/draw/src/defaults.test.js +18 -0
- package/plugins/draw/src/draw.scss +43 -0
- package/plugins/draw/src/events.js +237 -0
- package/plugins/draw/src/events.test.js +369 -0
- package/plugins/draw/src/index.js +10 -0
- package/plugins/draw/src/index.test.js +24 -0
- package/plugins/draw/src/manifest.js +170 -0
- package/plugins/draw/src/manifest.test.js +147 -0
- package/plugins/draw/src/reducer.js +69 -0
- package/plugins/draw/src/reducer.test.js +85 -0
- package/plugins/draw/src/utils/debounce.js +16 -0
- package/plugins/draw/src/utils/debounce.test.js +40 -0
- package/plugins/draw/src/utils/eventBus.js +36 -0
- package/plugins/draw/src/utils/eventBus.test.js +47 -0
- package/plugins/draw/src/utils/flattenStyleProperties.js +25 -0
- package/plugins/draw/src/utils/flattenStyleProperties.test.js +33 -0
- package/plugins/draw/src/utils/getValueForStyle.js +33 -0
- package/plugins/draw/src/utils/getValueForStyle.test.js +32 -0
- package/plugins/draw/src/utils/resolveColors.js +39 -0
- package/plugins/draw/src/utils/resolveColors.test.js +34 -0
- package/plugins/draw/src/utils/spatial.js +243 -0
- package/plugins/draw/src/utils/spatial.test.js +243 -0
- package/plugins/draw/src/utils/touchTarget.js +95 -0
- package/plugins/draw/src/utils/touchTarget.test.js +107 -0
- package/plugins/draw/src/utils/undoStack.js +31 -0
- package/plugins/draw/src/utils/undoStack.test.js +49 -0
- package/plugins/draw/src/validation/liveDrawChecks.js +120 -0
- package/plugins/draw/src/validation/liveDrawChecks.test.js +159 -0
- package/plugins/draw/src/validation/liveStroke.js +85 -0
- package/plugins/draw/src/validation/liveStroke.test.js +150 -0
- package/plugins/draw/src/validation/rules.areaError.test.js +12 -0
- package/plugins/draw/src/validation/rules.js +186 -0
- package/plugins/draw/src/validation/rules.test.js +120 -0
- package/plugins/draw/src/validation/validateGeometry.js +119 -0
- package/plugins/draw/src/validation/validateGeometry.test.js +200 -0
- package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
- package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
- package/plugins/interact/src/hooks/useInteractionHandlers.js +1 -1
- package/plugins/interact/src/hooks/useInteractionHandlers.test.js +1 -1
- package/plugins/interact/src/utils/buildStylesMap.js +1 -0
- package/plugins/search/dist/css/index.css +1 -317
- package/rollup.esm.mjs +35 -28
- package/sonar-project.properties +2 -2
- package/src/App/components/Attributions/Attributions.module.scss +1 -0
- package/src/App/components/Hints/Hints.module.scss +5 -4
- package/src/App/components/KeyboardHelp/KeyboardHelp.jsx +1 -1
- package/src/App/components/KeyboardHelp/KeyboardHelp.test.jsx +11 -0
- package/src/App/components/MoveControl/MoveControl.jsx +73 -9
- package/src/App/components/MoveControl/MoveControl.module.scss +12 -2
- package/src/App/components/MoveControl/MoveControl.test.jsx +136 -0
- package/src/App/hooks/useHintsAPI.js +67 -0
- package/src/App/hooks/useHintsAPI.test.js +145 -0
- package/src/App/renderer/PluginInits.jsx +4 -0
- package/src/App/renderer/PluginInits.test.jsx +7 -1
- package/src/InteractiveMap/InteractiveMap.js +20 -0
- package/src/InteractiveMap/InteractiveMap.test.js +7 -0
- package/src/config/events.js +4 -0
- package/src/scss/settings/_dimensions.scss +3 -0
- package/src/services/announcer.js +38 -3
- package/src/services/announcer.test.js +133 -1
- package/src/services/hints.js +3 -0
- package/src/services/hints.test.js +19 -0
- package/src/types.js +12 -0
- package/src/utils/detectInterfaceType.js +5 -1
- package/src/utils/isMac.js +15 -0
- package/src/utils/isMac.test.js +37 -0
- package/webpack.umd.mjs +1 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const DisabledMode = {
|
|
2
|
+
onSetup () {
|
|
3
|
+
return {} // Return empty state
|
|
4
|
+
},
|
|
5
|
+
|
|
6
|
+
onClick () {
|
|
7
|
+
// Prevent feature selection
|
|
8
|
+
return false
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
onKeyUp () {
|
|
12
|
+
return false
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
onDrag () {
|
|
16
|
+
return false
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
toDisplayFeatures (_state, geojson, display) {
|
|
20
|
+
geojson.properties.active = 'false'
|
|
21
|
+
display(geojson)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { DisabledMode } from './disabledMode.js'
|
|
2
|
+
|
|
3
|
+
describe('DisabledMode', () => {
|
|
4
|
+
test('onSetup returns an empty state', () => {
|
|
5
|
+
expect(DisabledMode.onSetup()).toEqual({})
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
test('interaction handlers are disabled (return false to block selection/drag/keys)', () => {
|
|
9
|
+
expect(DisabledMode.onClick()).toBe(false)
|
|
10
|
+
expect(DisabledMode.onKeyUp()).toBe(false)
|
|
11
|
+
expect(DisabledMode.onDrag()).toBe(false)
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
test('toDisplayFeatures marks the feature inactive and still displays it', () => {
|
|
15
|
+
const display = jest.fn()
|
|
16
|
+
const geojson = { properties: {} }
|
|
17
|
+
DisabledMode.toDisplayFeatures({}, geojson, display)
|
|
18
|
+
expect(geojson.properties.active).toBe('false')
|
|
19
|
+
expect(display).toHaveBeenCalledWith(geojson)
|
|
20
|
+
})
|
|
21
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import MapboxDraw from '@mapbox/mapbox-gl-draw'
|
|
2
|
+
import { isValidLineClick } from '../../../utils/spatial.js'
|
|
3
|
+
import { createDrawMode } from './createDrawMode.js'
|
|
4
|
+
|
|
5
|
+
// Extend the built-in mode via the package's public API (MapboxDraw.modes) rather than a deep internal import
|
|
6
|
+
export const DrawLineMode = createDrawMode(MapboxDraw.modes.draw_line_string, {
|
|
7
|
+
featureProp: 'line',
|
|
8
|
+
geometryType: 'LineString',
|
|
9
|
+
getCoords: (feature) => feature.coordinates,
|
|
10
|
+
validateClick: (feature) => isValidLineClick(feature.coordinates),
|
|
11
|
+
excludeFeatureIdFromSetup: true, // DrawLineString interprets featureId as "continue existing"
|
|
12
|
+
finishOnInvalidClick: true, // Clicking same spot (like double-click) finishes the line
|
|
13
|
+
// Display coords during drawing: [v0...vN, rubber_band]
|
|
14
|
+
getPlacedCoords: (geojson) => geojson.geometry.coordinates.slice(0, -1)
|
|
15
|
+
})
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { setup, firedWith, DrawLineMode } from './drawMode/__helpers__/harness.js'
|
|
2
|
+
|
|
3
|
+
const drawVertexMarkers = (ctx, state, geometry) => {
|
|
4
|
+
const display = jest.fn()
|
|
5
|
+
ctx.toDisplayFeatures(state, { type: 'Feature', properties: { id: state.line.id }, geometry }, display)
|
|
6
|
+
return display.mock.calls.map(([f]) => f)
|
|
7
|
+
.filter((f) => f.properties.meta === 'draw-vertex')
|
|
8
|
+
.map((m) => m.geometry.coordinates)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe('DrawLineMode config', () => {
|
|
12
|
+
test('excludeFeatureIdFromSetup starts a fresh line feature even when a featureId is supplied', () => {
|
|
13
|
+
const { state } = setup(DrawLineMode)
|
|
14
|
+
expect(state.line).toBeDefined()
|
|
15
|
+
expect(state.featureId).toBe('shape-1')
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('getCoords reads the line coordinates directly (not a ring)', () => {
|
|
19
|
+
const { ctx, state } = setup(DrawLineMode)
|
|
20
|
+
ctx.doClick(state)
|
|
21
|
+
expect(state.line.coordinates[0]).toEqual([5, 5]) // first placed vertex, at map centre
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test('getPlacedCoords marks every placed vertex, excluding the rubber band', () => {
|
|
25
|
+
const { ctx, state } = setup(DrawLineMode)
|
|
26
|
+
// Display coords: [v0..vN, rubber_band] → placed = slice(0, -1)
|
|
27
|
+
expect(drawVertexMarkers(ctx, state, { type: 'LineString', coordinates: [[5, 5], [8, 8]] })).toEqual([[5, 5]])
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test('finishOnInvalidClick finishes the line when the same spot is clicked twice', () => {
|
|
31
|
+
const { ctx, state } = setup(DrawLineMode)
|
|
32
|
+
ctx.doClick(state)
|
|
33
|
+
ctx.doClick(state) // same spot → finish (like a double-click)
|
|
34
|
+
expect(firedWith(ctx.map, 'draw.create')).toHaveLength(1)
|
|
35
|
+
expect(ctx.changeMode).toHaveBeenCalledWith('simple_select', { featureIds: [state.line.id] })
|
|
36
|
+
})
|
|
37
|
+
})
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { DrawPolygonMode } from '../../drawPolygonMode.js'
|
|
2
|
+
import { DrawLineMode } from '../../drawLineMode.js'
|
|
3
|
+
import { createUndoStack } from '../../../../../utils/undoStack.js'
|
|
4
|
+
import PolygonFeature from '../../../../../../../../node_modules/@mapbox/mapbox-gl-draw/src/feature_types/polygon.js'
|
|
5
|
+
import LineStringFeature from '../../../../../../../../node_modules/@mapbox/mapbox-gl-draw/src/feature_types/line_string.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Shared test harness for the createDrawMode handler modules. Exercises the real
|
|
9
|
+
* DrawPolygonMode / DrawLineMode (real mapbox-gl-draw parent modes and feature
|
|
10
|
+
* classes) over test doubles for the map, store and DOM. Excluded from coverage.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export { DrawPolygonMode, DrawLineMode }
|
|
14
|
+
export const CENTER = { lng: 5, lat: 5 }
|
|
15
|
+
|
|
16
|
+
const createMap = () => {
|
|
17
|
+
const canvas = document.createElement('canvas')
|
|
18
|
+
const listeners = {}
|
|
19
|
+
return {
|
|
20
|
+
_undoInProgress: false,
|
|
21
|
+
_undoStack: null,
|
|
22
|
+
_snapInstance: null,
|
|
23
|
+
doubleClickZoom: { disable: jest.fn(), enable: jest.fn() },
|
|
24
|
+
getCanvas: () => canvas,
|
|
25
|
+
getCenter: () => ({ ...CENTER }),
|
|
26
|
+
project: jest.fn(() => ({ x: 50, y: 50 })),
|
|
27
|
+
unproject: jest.fn(() => ({ ...CENTER })),
|
|
28
|
+
fire: jest.fn(function (type, e) { (listeners[type] ?? []).forEach((h) => h(e)) }),
|
|
29
|
+
on: jest.fn((type, h) => { (listeners[type] ??= []).push(h) }),
|
|
30
|
+
off: jest.fn((type, h) => { listeners[type] = (listeners[type] ?? []).filter((x) => x !== h) })
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// The `this` context mapbox-gl-draw gives a mode: map, ctx accessors and the mode's own methods
|
|
35
|
+
const createModeContext = (mode) => {
|
|
36
|
+
const map = createMap()
|
|
37
|
+
const features = new Map()
|
|
38
|
+
const store = {
|
|
39
|
+
add: jest.fn((f) => features.set(f.id, f)),
|
|
40
|
+
delete: jest.fn((ids) => ids.forEach((id) => features.delete(id))),
|
|
41
|
+
render: jest.fn(),
|
|
42
|
+
featureChanged: jest.fn(),
|
|
43
|
+
getInitialConfigValue: jest.fn(() => true)
|
|
44
|
+
}
|
|
45
|
+
const ctx = {
|
|
46
|
+
map,
|
|
47
|
+
_ctx: { store, api: { changeMode: jest.fn(), add: jest.fn(), delete: jest.fn() }, options: {} },
|
|
48
|
+
addFeature: (f) => store.add(f),
|
|
49
|
+
getFeature: (id) => features.get(id),
|
|
50
|
+
deleteFeature: jest.fn((ids) => ids.forEach((id) => features.delete(id))),
|
|
51
|
+
clearSelectedFeatures: jest.fn(),
|
|
52
|
+
updateUIClasses: jest.fn(),
|
|
53
|
+
activateUIButton: jest.fn(),
|
|
54
|
+
setActionableState: jest.fn(),
|
|
55
|
+
changeMode: jest.fn()
|
|
56
|
+
}
|
|
57
|
+
ctx.newFeature = (geojson) => geojson.geometry.type === 'Polygon'
|
|
58
|
+
? new PolygonFeature(ctx._ctx, geojson)
|
|
59
|
+
: new LineStringFeature(ctx._ctx, geojson)
|
|
60
|
+
Object.assign(ctx, mode)
|
|
61
|
+
contexts.push(ctx)
|
|
62
|
+
return ctx
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Remove window/container/map listeners registered by onSetup so tests don't leak into each other
|
|
66
|
+
const contexts = []
|
|
67
|
+
const removeListeners = (ctx) => ctx._listeners?.forEach(([t, e, h]) =>
|
|
68
|
+
t.removeEventListener ? t.removeEventListener(e, h) : t.off(e, h))
|
|
69
|
+
|
|
70
|
+
const createContainer = () => {
|
|
71
|
+
const container = document.createElement('div')
|
|
72
|
+
container.tabIndex = 0
|
|
73
|
+
const marker = document.createElement('div')
|
|
74
|
+
marker.id = 'vertex-marker'
|
|
75
|
+
container.appendChild(marker)
|
|
76
|
+
const button = document.createElement('button')
|
|
77
|
+
button.id = 'add-vertex'
|
|
78
|
+
container.appendChild(button)
|
|
79
|
+
document.body.appendChild(container)
|
|
80
|
+
return { container, marker, button }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const setup = (mode, options = {}) => {
|
|
84
|
+
const ctx = createModeContext(mode)
|
|
85
|
+
const dom = createContainer()
|
|
86
|
+
ctx.map._undoStack = createUndoStack(() => {})
|
|
87
|
+
const state = ctx.onSetup({
|
|
88
|
+
container: dom.container,
|
|
89
|
+
vertexMarkerId: 'vertex-marker',
|
|
90
|
+
addVertexButtonId: 'add-vertex',
|
|
91
|
+
interfaceType: 'mouse',
|
|
92
|
+
featureId: 'shape-1',
|
|
93
|
+
properties: { label: 'field' },
|
|
94
|
+
...options
|
|
95
|
+
})
|
|
96
|
+
return { ctx, state, ...dom }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export const clickEvent = (ctx, lng, lat, overrides = {}) => ({
|
|
100
|
+
lngLat: { lng, lat },
|
|
101
|
+
point: { x: lng, y: lat },
|
|
102
|
+
originalEvent: { button: 0, target: ctx.map.getCanvas(), ...overrides }
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
export const clickAt = (ctx, state, lng, lat) => ctx.onClick(state, clickEvent(ctx, lng, lat))
|
|
106
|
+
|
|
107
|
+
export const firedWith = (map, type) => map.fire.mock.calls.filter(([t]) => t === type).map(([, e]) => e)
|
|
108
|
+
|
|
109
|
+
export const activeSnap = () => ({ status: true, snapStatus: true, snapCoords: [9, 9], snapToClosestPoint: jest.fn() })
|
|
110
|
+
|
|
111
|
+
afterEach(() => {
|
|
112
|
+
contexts.splice(0).forEach(removeListeners)
|
|
113
|
+
document.body.innerHTML = ''
|
|
114
|
+
jest.useRealTimers()
|
|
115
|
+
})
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getSnapInstance, isSnapActive, isSnapEnabled, createSnappedEvent, createSnappedClickEvent
|
|
3
|
+
} from '../../utils/snapHelpers.js'
|
|
4
|
+
import { attemptPlacement } from '../../../../validation/validateGeometry.js'
|
|
5
|
+
|
|
6
|
+
// The commit-level geometrychange payload for a vertex commit: the placed-only
|
|
7
|
+
// geometry (trailing rubber-band point dropped so validation tests the committed shape).
|
|
8
|
+
const placedDrawGeometryChange = (feature, getCoords, phase) => {
|
|
9
|
+
const placed = getCoords(feature).slice(0, -1)
|
|
10
|
+
const type = feature.toGeoJSON().geometry.type
|
|
11
|
+
const geometry = type === 'Polygon'
|
|
12
|
+
? { type: 'Polygon', coordinates: [placed] }
|
|
13
|
+
: { type: 'LineString', coordinates: placed }
|
|
14
|
+
return { feature: { type: 'Feature', geometry, properties: {} }, phase, vertexIndex: Math.max(0, placed.length - 1) }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Fire a commit-level geometrychange for validation, deferred a tick so it runs after
|
|
18
|
+
// the current click settles.
|
|
19
|
+
const scheduleDrawValidation = (map, getFeature, getCoords, state, phase) => {
|
|
20
|
+
setTimeout(() => {
|
|
21
|
+
const feature = getFeature(state)
|
|
22
|
+
if (!feature) { return }
|
|
23
|
+
map.fire('draw.geometrychange', placedDrawGeometryChange(feature, getCoords, phase))
|
|
24
|
+
}, 0)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Re-id a freshly created feature to the caller's requested id.
|
|
28
|
+
const reidCreatedFeature = (api, feature, featureId) => {
|
|
29
|
+
api.delete(feature.id)
|
|
30
|
+
feature.id = featureId
|
|
31
|
+
api.add(feature, { userProperties: true })
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Guards, notifications, and small wiring handlers shared by the click paths.
|
|
35
|
+
const createClickHelpers = ({ geometryType, getFeature, getCoords }) => ({
|
|
36
|
+
// Non-drawing clicks: secondary buttons, clicks during an undo, or off-canvas.
|
|
37
|
+
_isIgnorableClick (e) {
|
|
38
|
+
return e.originalEvent.button > 0 || this.map._undoInProgress || e.originalEvent.target !== this.map.getCanvas()
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
// Attempt a placement through the shared hard rules + user callback. On a veto
|
|
42
|
+
// the vertex never appears and a draw.placementblocked event carries the
|
|
43
|
+
// reason. The trailing rubber-band coord is dropped so the candidate is placed
|
|
44
|
+
// vertices + the point about to be placed.
|
|
45
|
+
_canPlaceVertex (state, point) {
|
|
46
|
+
const feature = getFeature(state)
|
|
47
|
+
if (!feature || !point) { return true }
|
|
48
|
+
const result = attemptPlacement({
|
|
49
|
+
placed: getCoords(feature).slice(0, -1),
|
|
50
|
+
point,
|
|
51
|
+
geometryType,
|
|
52
|
+
onGeometryChange: this.map._drawGeometryValidator
|
|
53
|
+
})
|
|
54
|
+
if (!result.valid) {
|
|
55
|
+
this.map.fire('draw.placementblocked', result.blocked)
|
|
56
|
+
}
|
|
57
|
+
return result.valid
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
dispatchVertexChange (coords) {
|
|
61
|
+
// Both polygon ring and LineString store [v0...vN, rubber_band] during drawing — subtract 1 to get placed vertex count
|
|
62
|
+
this.map.fire('draw.vertexchange', {
|
|
63
|
+
numVertecies: Math.max(0, coords.length - 1)
|
|
64
|
+
})
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
// Emit a commit-level geometrychange after a vertex commit (placement or undo)
|
|
68
|
+
// so the validation layer can gate the Done button.
|
|
69
|
+
emitDrawValidation (state, phase = 'commit-add') {
|
|
70
|
+
scheduleDrawValidation(this.map, getFeature, getCoords, state, phase)
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
onTap () {
|
|
74
|
+
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
onVertexButtonClick (state, e) {
|
|
78
|
+
// Only trigger for the specific add vertex button, and skip during undo
|
|
79
|
+
if (state.addVertexButtonId && !this.map._undoInProgress && e.target.closest(`#${state.addVertexButtonId}`)) {
|
|
80
|
+
this.doClick(state)
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
onCreate (state, e) {
|
|
85
|
+
reidCreatedFeature(this._ctx.api, e.features[0], state.featureId)
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
// The click paths themselves: mouse clicks and the simulated crosshair click
|
|
90
|
+
// (touch / keyboard / add-vertex button).
|
|
91
|
+
const createClickActions = ({ ParentMode, getFeature, getCoords, validateClick, finishOnInvalidClick }) => {
|
|
92
|
+
// Snap is inactive: sync the trailing rubber-band point to the click, then guard
|
|
93
|
+
// against a duplicate-coordinate click reaching ParentMode, which would trigger a
|
|
94
|
+
// changeMode chain and cause a runtime error on coords.length access (polygon).
|
|
95
|
+
// Returns false when the click should be dropped without placing a vertex.
|
|
96
|
+
const prepareUnsnappedClick = (state, e) => {
|
|
97
|
+
const coords = getCoords(getFeature(state))
|
|
98
|
+
if (coords.length > 0) {
|
|
99
|
+
coords[coords.length - 1] = [e.lngLat.lng, e.lngLat.lat]
|
|
100
|
+
}
|
|
101
|
+
return finishOnInvalidClick || validateClick(getFeature(state))
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
onClick (state, e) {
|
|
106
|
+
// Skip non-primary clicks, undo operations, or clicks outside canvas
|
|
107
|
+
if (this._isIgnorableClick(e)) {
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
// Block a finish/close gesture (clicking a placed vertex) while the shape is invalid.
|
|
111
|
+
if (this.map._drawGeometryValid === false && e.featureTarget?.properties?.meta === 'vertex') {
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
const snap = getSnapInstance(this.map)
|
|
115
|
+
if (isSnapEnabled(state) && isSnapActive(snap)) {
|
|
116
|
+
e = createSnappedEvent(e, snap)
|
|
117
|
+
} else if (!prepareUnsnappedClick(state, e)) {
|
|
118
|
+
return
|
|
119
|
+
} else {
|
|
120
|
+
// No action
|
|
121
|
+
}
|
|
122
|
+
// Hard gate: a placement the rules or the user callback veto never appears, so
|
|
123
|
+
// an unrecoverable state (e.g. a self-crossing path) can't be drawn forward.
|
|
124
|
+
if (!this._canPlaceVertex(state, [e.lngLat.lng, e.lngLat.lat])) {
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
const coordsBefore = getCoords(getFeature(state)).length
|
|
128
|
+
ParentMode.onClick.call(this, state, e)
|
|
129
|
+
// Push undo and update count if a vertex was added
|
|
130
|
+
if (getCoords(getFeature(state)).length > coordsBefore) {
|
|
131
|
+
this.pushDrawUndo(state)
|
|
132
|
+
this.dispatchVertexChange(getCoords(getFeature(state)))
|
|
133
|
+
this.emitDrawValidation(state)
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
doClick (state) {
|
|
138
|
+
// Skip during undo operation
|
|
139
|
+
if (this.map._undoInProgress) {
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const feature = getFeature(state)
|
|
144
|
+
const coords = getCoords(feature)
|
|
145
|
+
this.dispatchVertexChange(coords)
|
|
146
|
+
|
|
147
|
+
if (!validateClick(feature)) {
|
|
148
|
+
// For lines: clicking same spot (like double-click) should finish the line — but
|
|
149
|
+
// only when the geometry is valid, so an invalid line can't be completed.
|
|
150
|
+
// isValidLineClick only returns false with 2+ coords, so coords.length is always > 1 here.
|
|
151
|
+
if (finishOnInvalidClick && this.map._drawGeometryValid !== false) {
|
|
152
|
+
coords.pop()
|
|
153
|
+
this.map.fire('draw.create', { features: [feature.toGeoJSON()] })
|
|
154
|
+
this.changeMode('simple_select', { featureIds: [feature.id] })
|
|
155
|
+
}
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const snap = getSnapInstance(this.map)
|
|
160
|
+
const snappedEvent = isSnapEnabled(state) && createSnappedClickEvent(this.map, snap)
|
|
161
|
+
|
|
162
|
+
// Hard-gate parity with onClick: touch/keyboard/add-button placements are vetoed
|
|
163
|
+
// at the point that would actually be committed (snapped or map centre).
|
|
164
|
+
const target = snappedEvent ? snappedEvent.lngLat : this.map.getCenter()
|
|
165
|
+
if (!this._canPlaceVertex(state, [target.lng, target.lat])) {
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (snappedEvent) {
|
|
170
|
+
ParentMode.onClick.call(this, state, snappedEvent)
|
|
171
|
+
this._ctx.store.render()
|
|
172
|
+
} else {
|
|
173
|
+
this._simulateMouse('click', ParentMode.onClick, state)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Push undo and update count if a vertex was added. A validated click always
|
|
177
|
+
// adds one vertex via the parent mode, so this runs on every successful doClick.
|
|
178
|
+
const newCoords = getCoords(getFeature(state))
|
|
179
|
+
this.pushDrawUndo(state)
|
|
180
|
+
this.dispatchVertexChange(newCoords)
|
|
181
|
+
this.emitDrawValidation(state)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Click / vertex-placement handling for the shared draw mode: mouse clicks, the
|
|
188
|
+
* add-vertex button, and the draw.create re-id step. Part of createDrawMode.
|
|
189
|
+
*/
|
|
190
|
+
export const createClickHandlers = (deps) => ({
|
|
191
|
+
...createClickHelpers(deps),
|
|
192
|
+
...createClickActions(deps)
|
|
193
|
+
})
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { setup, clickAt, clickEvent, firedWith, activeSnap, DrawPolygonMode, DrawLineMode, CENTER } from './__helpers__/harness.js'
|
|
2
|
+
|
|
3
|
+
describe('mouse clicks (polygon)', () => {
|
|
4
|
+
test('each click at a new position places a vertex, fires vertexchange and pushes an undo op', () => {
|
|
5
|
+
const { ctx, state } = setup(DrawPolygonMode)
|
|
6
|
+
clickAt(ctx, state, 0, 0)
|
|
7
|
+
clickAt(ctx, state, 10, 0)
|
|
8
|
+
|
|
9
|
+
// Ring is [v0, v1, rubber_band]
|
|
10
|
+
expect(state.polygon.coordinates[0]).toHaveLength(3)
|
|
11
|
+
expect(state.polygon.coordinates[0][0]).toEqual([0, 0])
|
|
12
|
+
expect(state.polygon.coordinates[0][1]).toEqual([10, 0])
|
|
13
|
+
expect(firedWith(ctx.map, 'draw.vertexchange').pop()).toEqual({ numVertecies: 2 })
|
|
14
|
+
expect(ctx.map._undoStack.length).toBe(2)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('clicking the same position again is rejected', () => {
|
|
18
|
+
const { ctx, state } = setup(DrawPolygonMode)
|
|
19
|
+
clickAt(ctx, state, 0, 0)
|
|
20
|
+
clickAt(ctx, state, 0, 0)
|
|
21
|
+
expect(state.polygon.coordinates[0]).toHaveLength(2)
|
|
22
|
+
expect(ctx.map._undoStack.length).toBe(1)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test('rejects a click that would make the drawn path cross itself', () => {
|
|
26
|
+
const { ctx, state } = setup(DrawPolygonMode)
|
|
27
|
+
clickAt(ctx, state, 0, 0)
|
|
28
|
+
clickAt(ctx, state, 10, 10)
|
|
29
|
+
clickAt(ctx, state, 10, 0)
|
|
30
|
+
const lenBefore = state.polygon.coordinates[0].length
|
|
31
|
+
clickAt(ctx, state, 0, 10) // the new edge would cross the (0,0)-(10,10) edge
|
|
32
|
+
expect(state.polygon.coordinates[0].length).toBe(lenBefore) // vertex never placed
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('a rejected placement fires draw.placementblocked with the reason', () => {
|
|
36
|
+
const { ctx, state } = setup(DrawPolygonMode)
|
|
37
|
+
clickAt(ctx, state, 0, 0)
|
|
38
|
+
clickAt(ctx, state, 10, 10)
|
|
39
|
+
clickAt(ctx, state, 10, 0)
|
|
40
|
+
clickAt(ctx, state, 0, 10)
|
|
41
|
+
expect(firedWith(ctx.map, 'draw.placementblocked').pop()).toEqual(expect.objectContaining({
|
|
42
|
+
phase: 'place',
|
|
43
|
+
mode: 'draw_polygon',
|
|
44
|
+
vertexIndex: 3,
|
|
45
|
+
reason: expect.any(String),
|
|
46
|
+
feature: expect.objectContaining({ type: 'Feature' })
|
|
47
|
+
}))
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('the user callback can veto a mouse placement (and receives phase "place")', () => {
|
|
51
|
+
const { ctx, state } = setup(DrawPolygonMode)
|
|
52
|
+
ctx.map._drawGeometryValidator = jest.fn((event) =>
|
|
53
|
+
event.phase === 'place' ? { valid: false, reason: 'outside region' } : { valid: true })
|
|
54
|
+
clickAt(ctx, state, 0, 0)
|
|
55
|
+
expect(state.polygon.coordinates[0]).toHaveLength(0) // first vertex vetoed
|
|
56
|
+
expect(firedWith(ctx.map, 'draw.placementblocked').pop()).toEqual(
|
|
57
|
+
expect.objectContaining({ reason: 'outside region', vertexIndex: 0 }))
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('non-primary buttons, undo-in-progress and off-canvas clicks are ignored', () => {
|
|
61
|
+
const { ctx, state } = setup(DrawPolygonMode)
|
|
62
|
+
ctx.onClick(state, clickEvent(ctx, 0, 0, { button: 2 }))
|
|
63
|
+
ctx.map._undoInProgress = true
|
|
64
|
+
clickAt(ctx, state, 0, 0)
|
|
65
|
+
ctx.map._undoInProgress = false
|
|
66
|
+
ctx.onClick(state, clickEvent(ctx, 0, 0, { target: document.body }))
|
|
67
|
+
expect(state.polygon.coordinates[0]).toHaveLength(0)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('with snap active the vertex is placed at the snapped position', () => {
|
|
71
|
+
const { ctx, state } = setup(DrawPolygonMode, { getSnapEnabled: () => true })
|
|
72
|
+
ctx.map._snapInstance = activeSnap()
|
|
73
|
+
clickAt(ctx, state, 0, 0)
|
|
74
|
+
expect(state.polygon.coordinates[0][0]).toEqual([9, 9])
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test('onTap is disabled', () => {
|
|
78
|
+
const { ctx, state } = setup(DrawPolygonMode)
|
|
79
|
+
expect(ctx.onTap(state, clickEvent(ctx, 0, 0))).toBeUndefined()
|
|
80
|
+
expect(state.polygon.coordinates[0]).toHaveLength(0)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test('placing a vertex emits a deferred commit-level geometrychange for validation', () => {
|
|
84
|
+
jest.useFakeTimers()
|
|
85
|
+
const { ctx, state } = setup(DrawPolygonMode)
|
|
86
|
+
clickAt(ctx, state, 0, 0)
|
|
87
|
+
clickAt(ctx, state, 10, 0)
|
|
88
|
+
jest.runAllTimers()
|
|
89
|
+
const geomChange = firedWith(ctx.map, 'draw.geometrychange').pop()
|
|
90
|
+
expect(geomChange).toEqual(expect.objectContaining({ phase: 'commit-add', feature: expect.any(Object) }))
|
|
91
|
+
jest.useRealTimers()
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test('emitDrawValidation does not fire without a feature', () => {
|
|
95
|
+
jest.useFakeTimers()
|
|
96
|
+
const { ctx } = setup(DrawPolygonMode)
|
|
97
|
+
ctx.emitDrawValidation({}) // no polygon on the state
|
|
98
|
+
jest.runAllTimers()
|
|
99
|
+
expect(firedWith(ctx.map, 'draw.geometrychange')).toHaveLength(0)
|
|
100
|
+
jest.useRealTimers()
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
test('a line placement emits a LineString geometrychange', () => {
|
|
104
|
+
jest.useFakeTimers()
|
|
105
|
+
const { ctx, state } = setup(DrawLineMode)
|
|
106
|
+
clickAt(ctx, state, 0, 0)
|
|
107
|
+
clickAt(ctx, state, 10, 0)
|
|
108
|
+
jest.runAllTimers()
|
|
109
|
+
const geom = firedWith(ctx.map, 'draw.geometrychange').pop()
|
|
110
|
+
expect(geom.feature.geometry.type).toBe('LineString')
|
|
111
|
+
jest.useRealTimers()
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
test('blocks a close gesture (vertex click) while the geometry is invalid', () => {
|
|
115
|
+
const { ctx, state } = setup(DrawPolygonMode)
|
|
116
|
+
clickAt(ctx, state, 0, 0)
|
|
117
|
+
clickAt(ctx, state, 10, 0)
|
|
118
|
+
clickAt(ctx, state, 5, 10)
|
|
119
|
+
ctx.map._drawGeometryValid = false
|
|
120
|
+
const lenBefore = state.polygon.coordinates[0].length
|
|
121
|
+
ctx.onClick(state, { ...clickEvent(ctx, 0, 0), featureTarget: { properties: { meta: 'vertex' } } })
|
|
122
|
+
expect(state.polygon.coordinates[0].length).toBe(lenBefore) // finish gesture ignored
|
|
123
|
+
expect(firedWith(ctx.map, 'draw.create')).toHaveLength(0)
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
describe('add-vertex button and doClick', () => {
|
|
128
|
+
test('clicking the add-vertex button places a vertex at map center', () => {
|
|
129
|
+
const { ctx, state, button } = setup(DrawPolygonMode, { interfaceType: 'keyboard' })
|
|
130
|
+
ctx.vertexButtonClickHandler({ target: button })
|
|
131
|
+
expect(state.polygon.coordinates[0][0]).toEqual([CENTER.lng, CENTER.lat])
|
|
132
|
+
expect(ctx.map._undoStack.length).toBe(1)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test('does not finish a line via doClick while the geometry is invalid', () => {
|
|
136
|
+
const { ctx, state } = setup(DrawLineMode)
|
|
137
|
+
clickAt(ctx, state, 0, 0)
|
|
138
|
+
clickAt(ctx, state, 10, 0)
|
|
139
|
+
ctx.map._drawGeometryValid = false
|
|
140
|
+
// Rubber-band duplicates the last placed vertex → a finish gesture.
|
|
141
|
+
state.line.setCoordinates([[0, 0], [10, 0], [10, 0]])
|
|
142
|
+
ctx.doClick(state)
|
|
143
|
+
expect(firedWith(ctx.map, 'draw.create')).toHaveLength(0)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
test('clicks elsewhere, without a button id, or during undo do nothing', () => {
|
|
147
|
+
const { ctx, state } = setup(DrawPolygonMode)
|
|
148
|
+
ctx.vertexButtonClickHandler({ target: document.body })
|
|
149
|
+
ctx.map._undoInProgress = true
|
|
150
|
+
ctx.doClick(state)
|
|
151
|
+
ctx.map._undoInProgress = false
|
|
152
|
+
const noButton = setup(DrawPolygonMode, { addVertexButtonId: null })
|
|
153
|
+
noButton.ctx.vertexButtonClickHandler({ target: document.body })
|
|
154
|
+
expect(state.polygon.coordinates[0]).toHaveLength(0)
|
|
155
|
+
expect(noButton.state.polygon.coordinates[0]).toHaveLength(0)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
test('doClick rejects a placement that would make the drawn path cross itself', () => {
|
|
159
|
+
const { ctx, state } = setup(DrawPolygonMode)
|
|
160
|
+
clickAt(ctx, state, 0, 0)
|
|
161
|
+
clickAt(ctx, state, 10, 0)
|
|
162
|
+
clickAt(ctx, state, 10, -10)
|
|
163
|
+
const lenBefore = state.polygon.coordinates[0].length
|
|
164
|
+
ctx.doClick(state) // map centre is (5,5): the new edge would cross (0,0)-(10,0)
|
|
165
|
+
expect(state.polygon.coordinates[0].length).toBe(lenBefore)
|
|
166
|
+
expect(firedWith(ctx.map, 'draw.placementblocked').length).toBeGreaterThan(0)
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
test('the user callback can veto a doClick placement', () => {
|
|
170
|
+
const { ctx, state } = setup(DrawPolygonMode)
|
|
171
|
+
ctx.map._drawGeometryValidator = () => ({ valid: false, reason: 'outside region' })
|
|
172
|
+
ctx.doClick(state)
|
|
173
|
+
expect(state.polygon.coordinates[0]).toHaveLength(0)
|
|
174
|
+
expect(firedWith(ctx.map, 'draw.placementblocked').pop()).toEqual(
|
|
175
|
+
expect.objectContaining({ reason: 'outside region', phase: 'place' }))
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
test('doClick places at the snapped position when snapping', () => {
|
|
179
|
+
const { ctx, state } = setup(DrawPolygonMode, { getSnapEnabled: () => true })
|
|
180
|
+
ctx.map._snapInstance = activeSnap()
|
|
181
|
+
ctx.doClick(state)
|
|
182
|
+
expect(state.polygon.coordinates[0][0]).toEqual([9, 9])
|
|
183
|
+
expect(ctx._ctx.store.render).toHaveBeenCalled()
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
test('line: activating add-vertex at the same spot twice finishes the line', () => {
|
|
187
|
+
const { ctx, state } = setup(DrawLineMode)
|
|
188
|
+
ctx.doClick(state)
|
|
189
|
+
ctx.doClick(state)
|
|
190
|
+
expect(firedWith(ctx.map, 'draw.create')).toHaveLength(1)
|
|
191
|
+
expect(ctx.changeMode).toHaveBeenCalledWith('simple_select', { featureIds: [state.line.id] })
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
test('polygon: a repeated add-vertex at the same spot is rejected without finishing', () => {
|
|
195
|
+
const { ctx, state } = setup(DrawPolygonMode)
|
|
196
|
+
ctx.doClick(state)
|
|
197
|
+
ctx.doClick(state)
|
|
198
|
+
expect(firedWith(ctx.map, 'draw.create')).toHaveLength(0)
|
|
199
|
+
expect(ctx.changeMode).not.toHaveBeenCalled()
|
|
200
|
+
})
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
describe('line mode mouse interactions', () => {
|
|
204
|
+
test('mouse clicks place line vertices; moving then undoing keeps the rubber band on the last vertex', () => {
|
|
205
|
+
const { ctx, state } = setup(DrawLineMode)
|
|
206
|
+
clickAt(ctx, state, 0, 0)
|
|
207
|
+
clickAt(ctx, state, 10, 0)
|
|
208
|
+
clickAt(ctx, state, 10, 10)
|
|
209
|
+
expect(state.line.coordinates.map(([x]) => x)).toEqual([0, 10, 10, 10]) // 3 placed + rubber band
|
|
210
|
+
|
|
211
|
+
ctx.onMouseMove(state, { lngLat: { lng: 3, lat: 3 }, point: { x: 3, y: 3 } })
|
|
212
|
+
expect(state.line.coordinates.at(-1)).toEqual([3, 3])
|
|
213
|
+
expect(firedWith(ctx.map, 'draw.geometrychange').length).toBeGreaterThan(0)
|
|
214
|
+
|
|
215
|
+
ctx.map._undoStack.push({ type: 'draw_vertex' })
|
|
216
|
+
ctx.undoVertex(state)
|
|
217
|
+
const coords = state.line.coordinates
|
|
218
|
+
expect(coords[coords.length - 1]).toEqual(coords[coords.length - 2])
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
test('a repeated mouse click at the last vertex adds no new vertex and does not finish the line', () => {
|
|
222
|
+
const { ctx, state } = setup(DrawLineMode)
|
|
223
|
+
clickAt(ctx, state, 0, 0)
|
|
224
|
+
clickAt(ctx, state, 10, 0)
|
|
225
|
+
const before = state.line.coordinates.length
|
|
226
|
+
clickAt(ctx, state, 10, 0)
|
|
227
|
+
expect(state.line.coordinates.length).toBe(before)
|
|
228
|
+
expect(firedWith(ctx.map, 'draw.create')).toHaveLength(0)
|
|
229
|
+
})
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
describe('onCreate (draw.create re-id)', () => {
|
|
233
|
+
test('re-ids the created feature to the requested featureId', () => {
|
|
234
|
+
const { ctx } = setup(DrawPolygonMode)
|
|
235
|
+
const feature = { id: 'temp-id' }
|
|
236
|
+
ctx.map.fire('draw.create', { features: [feature] })
|
|
237
|
+
expect(ctx._ctx.api.delete).toHaveBeenCalledWith('temp-id')
|
|
238
|
+
expect(feature.id).toBe('shape-1')
|
|
239
|
+
expect(ctx._ctx.api.add).toHaveBeenCalledWith(feature, { userProperties: true })
|
|
240
|
+
})
|
|
241
|
+
})
|