@defra/interactive-map 0.0.36-alpha → 0.0.38-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.
Files changed (248) hide show
  1. package/assets/templates/draw-tools.njk +4 -3
  2. package/dist/css/index.css +1 -1
  3. package/dist/esm/im-core.js +1 -1
  4. package/dist/esm/im-shell.js +1 -1
  5. package/dist/umd/im-core.js +1 -1
  6. package/dist/umd/index.js +1 -1
  7. package/docs/api.md +32 -0
  8. package/docs/examples/draw-tools.mdx +0 -4
  9. package/docs/plugins/draw.md +482 -0
  10. package/docs/plugins.md +2 -6
  11. package/govuk-prototype-kit.config.json +3 -2
  12. package/package.json +2 -4
  13. package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
  14. package/plugins/beta/draw-es/src/events.js +1 -1
  15. package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
  16. package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
  17. package/plugins/beta/draw-ol/dist/esm/im-draw-ol-plugin.js +1 -1
  18. package/plugins/beta/map-styles/dist/css/index.css +1 -97
  19. package/plugins/datasets/dist/esm/esriLayerAdapter.js +1 -1
  20. package/plugins/datasets/dist/esm/im-datasets-plugin.js +1 -1
  21. package/plugins/datasets/dist/umd/im-datasets-esri-adapter.js +1 -1
  22. package/plugins/datasets/dist/umd/im-datasets-plugin.js +1 -1
  23. package/plugins/datasets/src/adapters/esri/esriLayerAdapter.js +13 -0
  24. package/plugins/draw/dist/esm/im-draw-ml-adapter.js +1 -0
  25. package/plugins/draw/dist/esm/im-draw-ol-adapter.js +1 -0
  26. package/plugins/draw/dist/esm/im-draw-plugin.js +1 -0
  27. package/plugins/draw/dist/esm/index.js +1 -0
  28. package/plugins/draw/dist/umd/im-draw-ml-adapter.js +1 -0
  29. package/plugins/draw/dist/umd/im-draw-ol-adapter.js +2 -0
  30. package/plugins/draw/dist/umd/im-draw-ol-adapter.js.LICENSE.txt +1 -0
  31. package/plugins/draw/dist/umd/im-draw-plugin.js +2 -0
  32. package/plugins/draw/dist/umd/im-draw-plugin.js.LICENSE.txt +1 -0
  33. package/plugins/draw/dist/umd/index.js +2 -0
  34. package/plugins/draw/dist/umd/index.js.LICENSE.txt +1 -0
  35. package/plugins/draw/src/DrawInit.jsx +89 -0
  36. package/plugins/draw/src/DrawInit.test.jsx +184 -0
  37. package/plugins/draw/src/adapterEvents.js +127 -0
  38. package/plugins/draw/src/adapterEvents.test.js +20 -0
  39. package/plugins/draw/src/adapters/adapterContract.test.js +48 -0
  40. package/plugins/draw/src/adapters/loadDrawAdapter.js +14 -0
  41. package/plugins/draw/src/adapters/loadDrawAdapter.test.js +53 -0
  42. package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.js +367 -0
  43. package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.test.js +799 -0
  44. package/plugins/draw/src/adapters/maplibre/defaults.js +1 -0
  45. package/plugins/draw/src/adapters/maplibre/drawEvents.js +35 -0
  46. package/plugins/draw/src/adapters/maplibre/mapboxDraw.js +126 -0
  47. package/plugins/draw/src/adapters/maplibre/mapboxDraw.test.js +268 -0
  48. package/plugins/draw/src/adapters/maplibre/mapboxSnap.js +39 -0
  49. package/plugins/draw/src/adapters/maplibre/mapboxSnap.test.js +91 -0
  50. package/plugins/draw/src/adapters/maplibre/modes/createDrawMode.js +58 -0
  51. package/plugins/draw/src/adapters/maplibre/modes/createDrawMode.test.js +53 -0
  52. package/plugins/draw/src/adapters/maplibre/modes/disabledMode.js +23 -0
  53. package/plugins/draw/src/adapters/maplibre/modes/disabledMode.test.js +21 -0
  54. package/plugins/draw/src/adapters/maplibre/modes/drawLineMode.js +15 -0
  55. package/plugins/draw/src/adapters/maplibre/modes/drawLineMode.test.js +37 -0
  56. package/plugins/draw/src/adapters/maplibre/modes/drawMode/__helpers__/harness.js +115 -0
  57. package/plugins/draw/src/adapters/maplibre/modes/drawMode/clickHandlers.js +193 -0
  58. package/plugins/draw/src/adapters/maplibre/modes/drawMode/clickHandlers.test.js +241 -0
  59. package/plugins/draw/src/adapters/maplibre/modes/drawMode/keyboardHandlers.js +78 -0
  60. package/plugins/draw/src/adapters/maplibre/modes/drawMode/keyboardHandlers.test.js +75 -0
  61. package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.js +78 -0
  62. package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.test.js +48 -0
  63. package/plugins/draw/src/adapters/maplibre/modes/drawMode/pointerHandlers.js +103 -0
  64. package/plugins/draw/src/adapters/maplibre/modes/drawMode/pointerHandlers.test.js +127 -0
  65. package/plugins/draw/src/adapters/maplibre/modes/drawMode/renderHelpers.js +59 -0
  66. package/plugins/draw/src/adapters/maplibre/modes/drawMode/renderHelpers.test.js +34 -0
  67. package/plugins/draw/src/adapters/maplibre/modes/drawMode/undoHandlers.js +193 -0
  68. package/plugins/draw/src/adapters/maplibre/modes/drawMode/undoHandlers.test.js +195 -0
  69. package/plugins/draw/src/adapters/maplibre/modes/drawPolygonMode.js +16 -0
  70. package/plugins/draw/src/adapters/maplibre/modes/drawPolygonMode.test.js +27 -0
  71. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/__helpers__/harness.js +107 -0
  72. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/geometryHelpers.js +139 -0
  73. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/geometryHelpers.test.js +46 -0
  74. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/helpers.js +2 -0
  75. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/helpers.test.js +17 -0
  76. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.js +151 -0
  77. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.test.js +123 -0
  78. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/pointerHandlers.js +161 -0
  79. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/pointerHandlers.test.js +98 -0
  80. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.js +133 -0
  81. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.test.js +105 -0
  82. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.js +166 -0
  83. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.test.js +133 -0
  84. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.js +210 -0
  85. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.test.js +167 -0
  86. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexQueries.js +121 -0
  87. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexQueries.test.js +73 -0
  88. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.js +249 -0
  89. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.test.js +153 -0
  90. package/plugins/draw/src/adapters/maplibre/snap/constants.js +2 -0
  91. package/plugins/draw/src/adapters/maplibre/snap/mapHandlers.js +39 -0
  92. package/plugins/draw/src/adapters/maplibre/snap/mapHandlers.test.js +106 -0
  93. package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.js +148 -0
  94. package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.test.js +238 -0
  95. package/plugins/draw/src/adapters/maplibre/snap/snapInstance.js +115 -0
  96. package/plugins/draw/src/adapters/maplibre/snap/snapInstance.test.js +151 -0
  97. package/plugins/draw/src/adapters/maplibre/snap/sourceData.js +34 -0
  98. package/plugins/draw/src/adapters/maplibre/snap/sourceData.test.js +62 -0
  99. package/plugins/draw/src/adapters/maplibre/styles.js +207 -0
  100. package/plugins/draw/src/adapters/maplibre/styles.test.js +155 -0
  101. package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.js +202 -0
  102. package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.test.js +253 -0
  103. package/plugins/draw/src/adapters/maplibre/utils/touchClickWorkaround.js +57 -0
  104. package/plugins/draw/src/adapters/maplibre/utils/touchClickWorkaround.test.js +106 -0
  105. package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.js +117 -0
  106. package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.test.js +159 -0
  107. package/plugins/draw/src/adapters/openlayers/__helpers__/harness.js +90 -0
  108. package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.js +178 -0
  109. package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.test.js +153 -0
  110. package/plugins/draw/src/adapters/openlayers/core/featureStore.js +69 -0
  111. package/plugins/draw/src/adapters/openlayers/core/featureStore.test.js +58 -0
  112. package/plugins/draw/src/adapters/openlayers/core/internalEvents.js +8 -0
  113. package/plugins/draw/src/adapters/openlayers/core/styles.js +155 -0
  114. package/plugins/draw/src/adapters/openlayers/core/styles.test.js +143 -0
  115. package/plugins/draw/src/adapters/openlayers/defaults.js +1 -0
  116. package/plugins/draw/src/adapters/openlayers/draw/DrawMode.js +272 -0
  117. package/plugins/draw/src/adapters/openlayers/draw/DrawMode.test.js +431 -0
  118. package/plugins/draw/src/adapters/openlayers/draw/drawInput.js +139 -0
  119. package/plugins/draw/src/adapters/openlayers/draw/drawInput.test.js +140 -0
  120. package/plugins/draw/src/adapters/openlayers/draw/vertexPlacement.js +144 -0
  121. package/plugins/draw/src/adapters/openlayers/draw/vertexPlacement.test.js +196 -0
  122. package/plugins/draw/src/adapters/openlayers/edit/EditMode.js +385 -0
  123. package/plugins/draw/src/adapters/openlayers/edit/EditMode.test.js +303 -0
  124. package/plugins/draw/src/adapters/openlayers/edit/activeVertexLayer.js +55 -0
  125. package/plugins/draw/src/adapters/openlayers/edit/activeVertexLayer.test.js +53 -0
  126. package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.js +157 -0
  127. package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.test.js +195 -0
  128. package/plugins/draw/src/adapters/openlayers/edit/midpointLayer.js +57 -0
  129. package/plugins/draw/src/adapters/openlayers/edit/midpointLayer.test.js +42 -0
  130. package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.js +83 -0
  131. package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.test.js +80 -0
  132. package/plugins/draw/src/adapters/openlayers/edit/nudge.js +118 -0
  133. package/plugins/draw/src/adapters/openlayers/edit/nudge.test.js +165 -0
  134. package/plugins/draw/src/adapters/openlayers/edit/pointerHandlers.js +79 -0
  135. package/plugins/draw/src/adapters/openlayers/edit/pointerHandlers.test.js +78 -0
  136. package/plugins/draw/src/adapters/openlayers/edit/selectionState.js +116 -0
  137. package/plugins/draw/src/adapters/openlayers/edit/selectionState.test.js +109 -0
  138. package/plugins/draw/src/adapters/openlayers/edit/touchHandler.js +184 -0
  139. package/plugins/draw/src/adapters/openlayers/edit/touchHandler.test.js +133 -0
  140. package/plugins/draw/src/adapters/openlayers/edit/undoOps.js +94 -0
  141. package/plugins/draw/src/adapters/openlayers/edit/undoOps.test.js +64 -0
  142. package/plugins/draw/src/adapters/openlayers/edit/vertexHitTest.js +74 -0
  143. package/plugins/draw/src/adapters/openlayers/edit/vertexHitTest.test.js +28 -0
  144. package/plugins/draw/src/adapters/openlayers/edit/vertexLayer.js +49 -0
  145. package/plugins/draw/src/adapters/openlayers/edit/vertexLayer.test.js +42 -0
  146. package/plugins/draw/src/adapters/openlayers/edit/vertexOps.js +100 -0
  147. package/plugins/draw/src/adapters/openlayers/edit/vertexOps.test.js +80 -0
  148. package/plugins/draw/src/adapters/openlayers/olDraw.js +40 -0
  149. package/plugins/draw/src/adapters/openlayers/olDraw.test.js +70 -0
  150. package/plugins/draw/src/adapters/openlayers/snap/snapEngine.js +220 -0
  151. package/plugins/draw/src/adapters/openlayers/snap/snapEngine.test.js +232 -0
  152. package/plugins/draw/src/adapters/openlayers/snap/snapGeometry.js +198 -0
  153. package/plugins/draw/src/adapters/openlayers/snap/snapGeometry.test.js +136 -0
  154. package/plugins/draw/src/adapters/openlayers/snap/snapIndicator.js +81 -0
  155. package/plugins/draw/src/adapters/openlayers/snap/snapIndicator.test.js +72 -0
  156. package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.js +57 -0
  157. package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.test.js +68 -0
  158. package/plugins/draw/src/adapters/openlayers/snap/snapManager.js +99 -0
  159. package/plugins/draw/src/adapters/openlayers/snap/snapManager.test.js +101 -0
  160. package/plugins/draw/src/adapters/openlayers/utils/geometryHelpers.js +103 -0
  161. package/plugins/draw/src/adapters/openlayers/utils/geometryHelpers.test.js +76 -0
  162. package/plugins/draw/src/adapters/openlayers/utils/olCoords.js +35 -0
  163. package/plugins/draw/src/adapters/openlayers/utils/olCoords.test.js +22 -0
  164. package/plugins/draw/src/adapters/openlayers/utils/sketchHelpers.js +22 -0
  165. package/plugins/draw/src/adapters/openlayers/utils/sketchHelpers.test.js +18 -0
  166. package/plugins/draw/src/adapters/openlayers/utils/touchTarget.js +8 -0
  167. package/plugins/draw/src/api/addFeature.js +22 -0
  168. package/plugins/draw/src/api/addFeature.test.js +41 -0
  169. package/plugins/draw/src/api/deleteFeature.js +11 -0
  170. package/plugins/draw/src/api/deleteFeature.test.js +24 -0
  171. package/plugins/draw/src/api/editFeature.js +54 -0
  172. package/plugins/draw/src/api/editFeature.test.js +134 -0
  173. package/plugins/draw/src/api/merge.js +30 -0
  174. package/plugins/draw/src/api/merge.test.js +57 -0
  175. package/plugins/draw/src/api/newLine.js +41 -0
  176. package/plugins/draw/src/api/newLine.test.js +93 -0
  177. package/plugins/draw/src/api/newPolygon.js +41 -0
  178. package/plugins/draw/src/api/newPolygon.test.js +93 -0
  179. package/plugins/draw/src/api/split.js +117 -0
  180. package/plugins/draw/src/api/split.test.js +204 -0
  181. package/plugins/draw/src/defaults.js +52 -0
  182. package/plugins/draw/src/defaults.test.js +18 -0
  183. package/plugins/draw/src/draw.scss +43 -0
  184. package/plugins/draw/src/events.js +237 -0
  185. package/plugins/draw/src/events.test.js +369 -0
  186. package/plugins/draw/src/index.js +10 -0
  187. package/plugins/draw/src/index.test.js +24 -0
  188. package/plugins/draw/src/manifest.js +170 -0
  189. package/plugins/draw/src/manifest.test.js +147 -0
  190. package/plugins/draw/src/reducer.js +69 -0
  191. package/plugins/draw/src/reducer.test.js +85 -0
  192. package/plugins/draw/src/utils/debounce.js +16 -0
  193. package/plugins/draw/src/utils/debounce.test.js +40 -0
  194. package/plugins/draw/src/utils/eventBus.js +36 -0
  195. package/plugins/draw/src/utils/eventBus.test.js +47 -0
  196. package/plugins/draw/src/utils/flattenStyleProperties.js +25 -0
  197. package/plugins/draw/src/utils/flattenStyleProperties.test.js +33 -0
  198. package/plugins/draw/src/utils/getValueForStyle.js +33 -0
  199. package/plugins/draw/src/utils/getValueForStyle.test.js +32 -0
  200. package/plugins/draw/src/utils/resolveColors.js +39 -0
  201. package/plugins/draw/src/utils/resolveColors.test.js +34 -0
  202. package/plugins/draw/src/utils/spatial.js +243 -0
  203. package/plugins/draw/src/utils/spatial.test.js +243 -0
  204. package/plugins/draw/src/utils/touchTarget.js +95 -0
  205. package/plugins/draw/src/utils/touchTarget.test.js +107 -0
  206. package/plugins/draw/src/utils/undoStack.js +31 -0
  207. package/plugins/draw/src/utils/undoStack.test.js +49 -0
  208. package/plugins/draw/src/validation/liveDrawChecks.js +120 -0
  209. package/plugins/draw/src/validation/liveDrawChecks.test.js +159 -0
  210. package/plugins/draw/src/validation/liveStroke.js +85 -0
  211. package/plugins/draw/src/validation/liveStroke.test.js +150 -0
  212. package/plugins/draw/src/validation/rules.areaError.test.js +12 -0
  213. package/plugins/draw/src/validation/rules.js +186 -0
  214. package/plugins/draw/src/validation/rules.test.js +120 -0
  215. package/plugins/draw/src/validation/validateGeometry.js +119 -0
  216. package/plugins/draw/src/validation/validateGeometry.test.js +200 -0
  217. package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
  218. package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
  219. package/plugins/interact/src/hooks/useInteractionHandlers.js +1 -1
  220. package/plugins/interact/src/hooks/useInteractionHandlers.test.js +1 -1
  221. package/plugins/interact/src/utils/buildStylesMap.js +1 -0
  222. package/plugins/search/dist/css/index.css +1 -317
  223. package/rollup.esm.mjs +35 -28
  224. package/sonar-project.properties +2 -2
  225. package/src/App/components/Attributions/Attributions.module.scss +1 -0
  226. package/src/App/components/Hints/Hints.module.scss +5 -4
  227. package/src/App/components/KeyboardHelp/KeyboardHelp.jsx +1 -1
  228. package/src/App/components/KeyboardHelp/KeyboardHelp.test.jsx +11 -0
  229. package/src/App/components/MoveControl/MoveControl.jsx +73 -9
  230. package/src/App/components/MoveControl/MoveControl.module.scss +12 -2
  231. package/src/App/components/MoveControl/MoveControl.test.jsx +136 -0
  232. package/src/App/hooks/useHintsAPI.js +67 -0
  233. package/src/App/hooks/useHintsAPI.test.js +145 -0
  234. package/src/App/renderer/PluginInits.jsx +4 -0
  235. package/src/App/renderer/PluginInits.test.jsx +7 -1
  236. package/src/InteractiveMap/InteractiveMap.js +20 -0
  237. package/src/InteractiveMap/InteractiveMap.test.js +7 -0
  238. package/src/config/events.js +4 -0
  239. package/src/scss/settings/_dimensions.scss +3 -0
  240. package/src/services/announcer.js +38 -3
  241. package/src/services/announcer.test.js +133 -1
  242. package/src/services/hints.js +3 -0
  243. package/src/services/hints.test.js +19 -0
  244. package/src/types.js +12 -0
  245. package/src/utils/detectInterfaceType.js +5 -1
  246. package/src/utils/isMac.js +15 -0
  247. package/src/utils/isMac.test.js +37 -0
  248. package/webpack.umd.mjs +1 -0
@@ -0,0 +1,133 @@
1
+ import {
2
+ getSnapInstance, isSnapEnabled, triggerSnapAtPoint, getSnapLngLat,
3
+ clearSnapState, clearSnapIndicator
4
+ } from '../../utils/snapHelpers.js'
5
+ import { coordPathToFlatIndex } from './geometryHelpers.js'
6
+ import { isOnSVG } from './helpers.js'
7
+ import { createTouchTarget, applyTouchTargetColors } from '../../../../utils/touchTarget.js'
8
+ import { resolveColors } from '../../../../utils/resolveColors.js'
9
+
10
+ export const applyTouchVertexColors = (el, mapStyle, pluginConfig = {}) => {
11
+ if (!el) { return }
12
+ const { editActive, editHalo, editVertex } = resolveColors(mapStyle, pluginConfig)
13
+ applyTouchTargetColors(el, { editActive, editHalo, editVertex })
14
+ }
15
+
16
+ export const touchHandlers = {
17
+ addTouchVertexTarget (state) {
18
+ state.touchVertexTarget = createTouchTarget(state.container)
19
+ applyTouchVertexColors(state.touchVertexTarget, this.map._drawCurrentMapStyle, this.map._drawPluginConfig)
20
+ },
21
+
22
+ updateTouchVertexTarget (state, point) {
23
+ if (point && state.interfaceType === 'touch' && state.selectedVertexIndex >= 0) {
24
+ Object.assign(state.touchVertexTarget.style, { display: 'block', top: `${point.y}px`, left: `${point.x}px` })
25
+ } else {
26
+ state.touchVertexTarget.style.display = 'none'
27
+ }
28
+ },
29
+
30
+ hideTouchVertexIndicator (state) {
31
+ state.touchVertexTarget.style.display = 'none'
32
+ },
33
+
34
+ onPointerevent (state, e) {
35
+ state.interfaceType = e.pointerType === 'touch' ? 'touch' : 'mouse'
36
+ state.isPanEnabled = true
37
+ if (e.pointerType === 'touch' && e.type === 'pointermove' && !isOnSVG(e.target.parentNode) && !state._ignorePointermoveDeselect) {
38
+ this.changeMode(state, { selectedVertexIndex: -1, selectedVertexType: null, coordPath: null })
39
+ }
40
+ },
41
+
42
+ // Empty stubs required by DirectSelect
43
+ onTouchStart () {},
44
+ onTouchMove () {},
45
+ onTouchEnd () {},
46
+
47
+ onTouchend (state) {
48
+ clearSnapState(getSnapInstance(this.map))
49
+ if (state?.featureId) {
50
+ this.syncVertices(state)
51
+
52
+ // Push undo for the move if touch actually moved
53
+ if (state._touchMoved && state._moveStartPosition && state._moveStartIndex !== undefined) {
54
+ this.pushUndo({
55
+ type: 'move_vertex',
56
+ featureId: state.featureId,
57
+ vertexIndex: state._moveStartIndex,
58
+ previousPosition: state._moveStartPosition
59
+ })
60
+ }
61
+ state._moveStartPosition = null
62
+ state._moveStartIndex = undefined
63
+ state._touchMoved = false
64
+ }
65
+ },
66
+
67
+ onTap (state, e) {
68
+ // Hide snap indicator on any tap
69
+ const snap = getSnapInstance(this.map)
70
+ if (snap) {
71
+ clearSnapIndicator(snap, this.map)
72
+ }
73
+
74
+ const meta = e.featureTarget?.properties.meta
75
+ const coordPath = e.featureTarget?.properties.coord_path
76
+
77
+ if (meta === 'vertex') {
78
+ const feature = this.getFeature(state.featureId)
79
+ const idx = coordPathToFlatIndex(feature, coordPath)
80
+ this.changeMode(state, {
81
+ selectedVertexIndex: idx,
82
+ selectedVertexType: 'vertex',
83
+ coordPath
84
+ })
85
+ } else if (meta === 'midpoint') {
86
+ this.insertVertex({ ...state, selectedVertexIndex: this.getVertexIndexFromMidpoint(state, coordPath), selectedVertexType: 'midpoint' })
87
+ } else {
88
+ this.clickNoTarget(state)
89
+ }
90
+ },
91
+
92
+ onTouchstart (state, e) {
93
+ clearSnapState(getSnapInstance(this.map))
94
+ // Always get fresh vertex data in case coordinates changed during previous edits
95
+ const freshVertices = this.getVerticies(state.featureId)
96
+ const vertex = freshVertices?.[state.selectedVertexIndex]
97
+ if (!vertex || !isOnSVG(e.target.parentNode)) {
98
+ return
99
+ }
100
+
101
+ // Save starting position for undo
102
+ state._moveStartPosition = [...vertex]
103
+ state._moveStartIndex = state.selectedVertexIndex
104
+ state._touchMoved = false
105
+
106
+ const touch = { x: e.touches[0].clientX, y: e.touches[0].clientY }
107
+ const style = window.getComputedStyle(state.touchVertexTarget)
108
+ state.deltaTarget = { x: touch.x - Number.parseFloat(style.left), y: touch.y - Number.parseFloat(style.top) }
109
+ const vertexPt = this.map.project(vertex)
110
+ state.deltaVertex = { x: (touch.x / state.scale) - vertexPt.x, y: (touch.y / state.scale) - vertexPt.y }
111
+ },
112
+
113
+ onTouchmove (state, e) {
114
+ if (state.selectedVertexIndex < 0 || !isOnSVG(e.target.parentNode)) {
115
+ return
116
+ }
117
+
118
+ state._touchMoved = true
119
+
120
+ const touch = { x: e.touches[0].clientX, y: e.touches[0].clientY }
121
+ const screenPt = { x: (touch.x / state.scale) - state.deltaVertex.x, y: (touch.y / state.scale) - state.deltaVertex.y }
122
+
123
+ let finalCoord = this.map.unproject(screenPt)
124
+ if (isSnapEnabled(state)) {
125
+ const snap = getSnapInstance(this.map)
126
+ triggerSnapAtPoint(snap, this.map, screenPt)
127
+ finalCoord = getSnapLngLat(snap) || finalCoord
128
+ }
129
+
130
+ this.moveVertex(state, finalCoord)
131
+ this.updateTouchVertexTarget(state, { x: touch.x - state.deltaTarget.x, y: touch.y - state.deltaTarget.y })
132
+ }
133
+ }
@@ -0,0 +1,105 @@
1
+ import { createHarness, svgTarget } from './__helpers__/harness.js'
2
+ import { applyTouchVertexColors } from './touchHandlers.js'
3
+
4
+ describe('touchHandlers', () => {
5
+ test('applyTouchVertexColors returns early for a null element and defaults the colour scheme', () => {
6
+ expect(() => applyTouchVertexColors(null, {})).not.toThrow()
7
+ const { state } = createHarness()
8
+ expect(() => applyTouchVertexColors(state.touchVertexTarget, null)).not.toThrow()
9
+ })
10
+
11
+ test('applyTouchVertexColors applies a pluginConfig override to the CSS custom properties', () => {
12
+ const el = document.createElement('div')
13
+ applyTouchVertexColors(el, { mapColorScheme: 'light' }, { editVertex: '#custom' })
14
+ expect(el.style.getPropertyValue('--draw-primary')).toBe('#custom')
15
+ })
16
+
17
+ test('addTouchVertexTarget resolves colours from map._drawPluginConfig', () => {
18
+ const { ctx, state, map } = createHarness()
19
+ map._drawPluginConfig = { editVertex: '#custom' }
20
+ ctx.addTouchVertexTarget(state)
21
+ expect(state.touchVertexTarget.style.getPropertyValue('--draw-primary')).toBe('#custom')
22
+ })
23
+
24
+ test('updateTouchVertexTarget shows the target for a touch selection and hides it otherwise', () => {
25
+ const { ctx, state } = createHarness()
26
+ state.interfaceType = 'touch'
27
+ state.selectedVertexIndex = 0
28
+ ctx.updateTouchVertexTarget(state, { x: 12, y: 34 })
29
+ expect(state.touchVertexTarget.style.display).toBe('block')
30
+ ctx.updateTouchVertexTarget(state, null)
31
+ expect(state.touchVertexTarget.style.display).toBe('none')
32
+ })
33
+
34
+ test('onPointerevent deselects on touch drag off the target, and just tracks the interface for mouse', () => {
35
+ const { ctx, state, api } = createHarness()
36
+ ctx.onPointerevent(state, { pointerType: 'mouse', type: 'pointermove', target: { parentNode: document.createElement('div') } })
37
+ expect(state.interfaceType).toBe('mouse')
38
+ expect(api.changeMode).not.toHaveBeenCalled()
39
+
40
+ state._ignorePointermoveDeselect = false
41
+ ctx.onPointerevent(state, { pointerType: 'touch', type: 'pointermove', target: { parentNode: document.createElement('div') } })
42
+ expect(state.interfaceType).toBe('touch')
43
+ expect(api.changeMode).toHaveBeenCalledWith('edit_vertex', expect.objectContaining({ selectedVertexIndex: -1 }))
44
+ })
45
+
46
+ test('onTouchend records a move undo only when a touch move occurred', () => {
47
+ const { ctx, state, map } = createHarness()
48
+ ctx.onTouchend({ }) // no featureId → no-op
49
+ ctx.onTouchend({ ...state, _touchMoved: false }) // feature but no move → no undo
50
+ expect(map._undoStack.length).toBe(0)
51
+ state._touchMoved = true
52
+ state._moveStartPosition = [0, 0]
53
+ state._moveStartIndex = 1
54
+ ctx.onTouchend(state)
55
+ expect(map._undoStack.pop()).toMatchObject({ type: 'move_vertex', vertexIndex: 1 })
56
+ })
57
+
58
+ test('onTouchstart captures move start state, ignoring taps off the vertex/target', () => {
59
+ const { ctx, state } = createHarness()
60
+ ctx.onTouchstart(state, { target: { parentNode: document.createElement('div') }, touches: [{ clientX: 1, clientY: 1 }] })
61
+ expect(state._moveStartIndex).toBeUndefined()
62
+
63
+ state.selectedVertexIndex = 1
64
+ ctx.onTouchstart(state, { target: svgTarget(state), touches: [{ clientX: 20, clientY: 20 }] })
65
+ expect(state._moveStartIndex).toBe(1)
66
+ expect(state._touchMoved).toBe(false)
67
+ })
68
+
69
+ test('onTouchmove moves the selected vertex, honouring snap, and ignores non-target moves', () => {
70
+ const { ctx, state, map } = createHarness()
71
+ state.selectedVertexIndex = 1
72
+ ctx.onTouchstart(state, { target: svgTarget(state), touches: [{ clientX: 20, clientY: 20 }] })
73
+
74
+ ctx.onTouchmove(state, { target: { parentNode: document.createElement('div') }, touches: [{ clientX: 5, clientY: 5 }] })
75
+ expect(state.vertecies[1]).toEqual([10, 0]) // off-target move ignored
76
+ ctx.onTouchmove(state, { target: svgTarget(state), touches: [{ clientX: 30, clientY: 40 }] })
77
+
78
+ state.getSnapEnabled = () => true
79
+ map._snapInstance = { status: true, snapStatus: true, snapCoords: [7, 8], snapToClosestPoint: jest.fn() }
80
+ ctx.onTouchmove(state, { target: svgTarget(state), touches: [{ clientX: 50, clientY: 60 }] })
81
+ expect(state.vertecies[1]).toEqual([7, 8])
82
+
83
+ // Snap enabled but no snap point → falls back to the pointer position
84
+ map._snapInstance = { status: true, snapStatus: false, snapCoords: null, snapToClosestPoint: jest.fn() }
85
+ ctx.onTouchmove(state, { target: svgTarget(state), touches: [{ clientX: 35, clientY: 45 }] })
86
+ expect(state._touchMoved).toBe(true)
87
+ })
88
+
89
+ test('onTap clears the snap indicator, then selects a vertex, inserts on a midpoint, or clears with no target', () => {
90
+ const { ctx, state, api, map } = createHarness()
91
+ map._snapInstance = { status: true, snapStatus: true, snapCoords: [1, 1] }
92
+ ctx.onTap(state, { featureTarget: { properties: { meta: 'vertex', coord_path: '0.2' } } })
93
+ expect(map.getLayer).toHaveBeenCalledWith('snap-helper-circle')
94
+ expect(api.changeMode).toHaveBeenCalledWith('edit_vertex', expect.objectContaining({ selectedVertexType: 'vertex' }))
95
+
96
+ api.changeMode.mockClear()
97
+ ctx.onTap(state, { featureTarget: { properties: { meta: 'midpoint', coord_path: '0.1' } } })
98
+ expect(api.add).toHaveBeenCalled() // insertVertex path
99
+
100
+ api.changeMode.mockClear()
101
+ map._snapInstance = null // no snap indicator to clear
102
+ ctx.onTap(state, { featureTarget: undefined })
103
+ expect(api.changeMode).toHaveBeenCalledWith('edit_vertex', expect.objectContaining({ selectedVertexIndex: -1 }))
104
+ })
105
+ })
@@ -0,0 +1,166 @@
1
+ import {
2
+ getRingSegments,
3
+ getSegmentForIndex,
4
+ getModifiableCoords
5
+ } from './geometryHelpers.js'
6
+ import { scalePoint } from './helpers.js'
7
+
8
+ // Map an undo-stack op type onto the geometry-change `phase` consumed by validation.
9
+ const UNDO_OP_PHASE = {
10
+ move_vertex: 'commit-move',
11
+ insert_vertex: 'commit-insert',
12
+ delete_vertex: 'commit-delete'
13
+ }
14
+
15
+ // Undoing an op commits the inverse change (undo of a delete re-inserts, etc.),
16
+ // so its re-validation reports the inverse phase.
17
+ const UNDO_INVERSE_PHASE = {
18
+ move_vertex: 'commit-move',
19
+ insert_vertex: 'commit-delete',
20
+ delete_vertex: 'commit-insert'
21
+ }
22
+
23
+ export const undoHandlers = {
24
+ // Fire geometry change event (for external listeners)
25
+ fireGeometryChange (state) {
26
+ const feature = this.getFeature(state.featureId)
27
+ if (feature) {
28
+ this.map.fire('draw.update', {
29
+ features: [feature.toGeoJSON()],
30
+ action: 'change_coordinates'
31
+ })
32
+ }
33
+ },
34
+
35
+ // Emit a commit-level geometrychange (feature + change phase + vertex index) so the
36
+ // validation layer can accept or reject the change. Deferred a tick to avoid
37
+ // re-entrancy: rejection calls draw.undo(), which must run after the current
38
+ // mutation (and its undo bookkeeping) has fully settled.
39
+ emitGeometryValidation (phase, vertexIndex, featureId) {
40
+ if (!phase) { return }
41
+ setTimeout(() => {
42
+ const feature = this.getFeature(featureId)
43
+ if (!feature) { return }
44
+ this.map.fire('draw.geometrychange', { feature: feature.toGeoJSON(), phase, vertexIndex })
45
+ }, 0)
46
+ },
47
+
48
+ // Undo support
49
+ pushUndo (operation) {
50
+ const undoStack = this.map._undoStack
51
+ if (!undoStack) {
52
+ return
53
+ }
54
+ undoStack.push(operation)
55
+ // Every edit commit (move/insert/delete, via mouse or keyboard) records an undo
56
+ // op here, so this is the single point that feeds commit-level validation.
57
+ this.emitGeometryValidation(UNDO_OP_PHASE[operation.type], operation.vertexIndex, operation.featureId)
58
+ },
59
+
60
+ handleUndo (state) {
61
+ const undoStack = this.map._undoStack
62
+ if (!undoStack || undoStack.length === 0) {
63
+ return
64
+ }
65
+
66
+ const op = undoStack.pop()
67
+
68
+ if (op.type === 'move_vertex') {
69
+ this.undoMoveVertex(state, op)
70
+ } else if (op.type === 'insert_vertex') {
71
+ this.undoInsertVertex(state, op)
72
+ } else if (op.type === 'delete_vertex') {
73
+ this.undoDeleteVertex(state, op)
74
+ } else {
75
+ // No action
76
+ }
77
+ // An undo commits the inverse change, so it must re-validate like any other
78
+ // commit — otherwise the invalid stroke and the Done gate go stale.
79
+ this.emitGeometryValidation(UNDO_INVERSE_PHASE[op.type], op.vertexIndex, op.featureId)
80
+ },
81
+
82
+ undoMoveVertex (state, op) {
83
+ const { vertexIndex, previousPosition, featureId } = op
84
+ const feature = this.getFeature(featureId)
85
+ if (!feature) { return }
86
+
87
+ const geojson = feature.toGeoJSON()
88
+ const segments = getRingSegments(feature)
89
+ const result = getSegmentForIndex(segments, vertexIndex)
90
+ if (!result) { return }
91
+
92
+ const coords = getModifiableCoords(geojson, result.segment.path)
93
+ coords[result.localIdx] = previousPosition
94
+ this._applyUndoAndSync(state, geojson, featureId)
95
+
96
+ // Update touch vertex target position
97
+ const vertex = state.vertecies[state.selectedVertexIndex]
98
+ if (vertex) {
99
+ this.updateTouchVertexTarget(state, scalePoint(this.map.project(vertex), state.scale))
100
+ }
101
+ },
102
+
103
+ undoInsertVertex (state, op) {
104
+ const { vertexIndex, featureId } = op
105
+ const feature = this.getFeature(featureId)
106
+ if (!feature) { return }
107
+
108
+ const geojson = feature.toGeoJSON()
109
+ const segments = getRingSegments(feature)
110
+ const result = getSegmentForIndex(segments, vertexIndex)
111
+ if (!result) { return }
112
+
113
+ const coords = getModifiableCoords(geojson, result.segment.path)
114
+ coords.splice(result.localIdx, 1)
115
+ this._applyUndoAndSync(state, geojson, featureId)
116
+
117
+ // Clear DirectSelect's coordinate selection
118
+ this.clearSelectedCoordinates()
119
+ this.hideTouchVertexIndicator(state)
120
+ this.changeMode(state, { selectedVertexIndex: -1, selectedVertexType: null })
121
+ },
122
+
123
+ undoDeleteVertex (state, op) {
124
+ const { vertexIndex, position, featureId } = op
125
+ const feature = this.getFeature(featureId)
126
+ if (!feature) {
127
+ return
128
+ }
129
+
130
+ const geojson = feature.toGeoJSON()
131
+ const segments = getRingSegments(feature)
132
+
133
+ // Try to find segment containing vertexIndex
134
+ let result = getSegmentForIndex(segments, vertexIndex)
135
+
136
+ // If not found, vertex might be at segment boundary
137
+ if (!result) {
138
+ for (const seg of segments) {
139
+ if (vertexIndex === seg.start + seg.length) {
140
+ result = { segment: seg, localIdx: seg.length }
141
+ break
142
+ }
143
+ }
144
+ }
145
+
146
+ if (!result) {
147
+ return
148
+ }
149
+
150
+ const coords = getModifiableCoords(geojson, result.segment.path)
151
+ coords.splice(result.localIdx, 0, position)
152
+ this._applyUndoAndSync(state, geojson, featureId)
153
+
154
+ // Re-insertion always lands the vertex back at vertexIndex, so it is guaranteed present
155
+ this.updateTouchVertexTarget(state, scalePoint(this.map.project(state.vertecies[vertexIndex]), state.scale))
156
+ this.changeMode(state, { selectedVertexIndex: vertexIndex, selectedVertexType: 'vertex', coordPath: this.getCoordPath(state, vertexIndex) })
157
+ },
158
+
159
+ _applyUndoAndSync (state, geojson, featureId) {
160
+ this._ctx.api.add(geojson)
161
+ state.vertecies = this.getVerticies(featureId)
162
+ state.midpoints = this.getMidpoints(featureId)
163
+ this._ctx.store.render()
164
+ this.fireGeometryChange(state)
165
+ }
166
+ }
@@ -0,0 +1,133 @@
1
+ import { createHarness } from './__helpers__/harness.js'
2
+ import { createUndoStack } from '../../../../utils/undoStack.js'
3
+ import { getRingSegments } from './geometryHelpers.js'
4
+
5
+ describe('undoHandlers', () => {
6
+ test('pushUndo is a no-op without a stack and pushes otherwise', () => {
7
+ const { ctx, map } = createHarness()
8
+ map._undoStack = null
9
+ expect(() => ctx.pushUndo({ type: 'move_vertex' })).not.toThrow()
10
+ map._undoStack = createUndoStack(() => {})
11
+ ctx.pushUndo({ type: 'move_vertex' })
12
+ expect(map._undoStack.length).toBe(1)
13
+ })
14
+
15
+ test('pushUndo emits a deferred commit-level geometrychange with the change phase', () => {
16
+ jest.useFakeTimers()
17
+ const { ctx, map } = createHarness()
18
+ map.fire.mockClear()
19
+
20
+ ctx.pushUndo({ type: 'move_vertex', featureId: 'feat-1', vertexIndex: 2 })
21
+ // Deferred a tick to avoid re-entrancy — nothing fired synchronously.
22
+ expect(map.fire).not.toHaveBeenCalledWith('draw.geometrychange', expect.anything())
23
+
24
+ jest.runAllTimers()
25
+ expect(map.fire).toHaveBeenCalledWith('draw.geometrychange', expect.objectContaining({
26
+ phase: 'commit-move',
27
+ vertexIndex: 2,
28
+ feature: expect.any(Object)
29
+ }))
30
+ jest.useRealTimers()
31
+ })
32
+
33
+ test('pushUndo does not emit a geometrychange for unmapped op types', () => {
34
+ jest.useFakeTimers()
35
+ const { ctx, map } = createHarness()
36
+ map.fire.mockClear()
37
+ ctx.pushUndo({ type: 'draw_vertex', featureId: 'feat-1' })
38
+ jest.runAllTimers()
39
+ expect(map.fire).not.toHaveBeenCalledWith('draw.geometrychange', expect.anything())
40
+ jest.useRealTimers()
41
+ })
42
+
43
+ test('emitGeometryValidation does not fire once the feature is gone', () => {
44
+ jest.useFakeTimers()
45
+ const { ctx, map } = createHarness()
46
+ map.fire.mockClear()
47
+ ctx.emitGeometryValidation('commit-move', 0, 'missing-feature')
48
+ jest.runAllTimers()
49
+ expect(map.fire).not.toHaveBeenCalledWith('draw.geometrychange', expect.anything())
50
+ jest.useRealTimers()
51
+ })
52
+
53
+ test('handleUndo ignores an empty stack and dispatches by operation type', () => {
54
+ const { ctx, state, map } = createHarness()
55
+ ctx.handleUndo(state) // empty → no throw
56
+ const spies = ['undoMoveVertex', 'undoInsertVertex', 'undoDeleteVertex'].map((m) => jest.spyOn(ctx, m).mockImplementation(() => {}))
57
+ map._undoStack.push({ type: 'move_vertex', featureId: 'feat-1', vertexIndex: 0, previousPosition: [0, 0] })
58
+ ctx.handleUndo(state)
59
+ map._undoStack.push({ type: 'insert_vertex', featureId: 'feat-1', vertexIndex: 1 })
60
+ ctx.handleUndo(state)
61
+ map._undoStack.push({ type: 'delete_vertex', featureId: 'feat-1', vertexIndex: 1, position: [0, 0] })
62
+ ctx.handleUndo(state)
63
+ expect(spies.every((s) => s.mock.calls.length === 1)).toBe(true)
64
+
65
+ map._undoStack.push({ type: 'unknown' })
66
+ expect(() => ctx.handleUndo(state)).not.toThrow()
67
+ })
68
+
69
+ test('handleUndo re-validates with the inverse change phase (undo of a delete re-inserts)', () => {
70
+ jest.useFakeTimers()
71
+ const { ctx, state, map } = createHarness()
72
+ jest.spyOn(ctx, 'undoDeleteVertex').mockImplementation(() => {})
73
+ map._undoStack.push({ type: 'delete_vertex', featureId: 'feat-1', vertexIndex: 1, position: [0, 0] })
74
+ map.fire.mockClear()
75
+ ctx.handleUndo(state)
76
+ jest.runAllTimers()
77
+ expect(map.fire).toHaveBeenCalledWith('draw.geometrychange', expect.objectContaining({
78
+ phase: 'commit-insert',
79
+ vertexIndex: 1,
80
+ feature: expect.any(Object)
81
+ }))
82
+ jest.useRealTimers()
83
+ })
84
+
85
+ test('fireGeometryChange fires draw.update only when the feature exists', () => {
86
+ const { ctx, state, map } = createHarness()
87
+ ctx.fireGeometryChange(state)
88
+ expect(map.fire).toHaveBeenCalledWith('draw.update', expect.objectContaining({ action: 'change_coordinates' }))
89
+ map.fire.mockClear()
90
+ ctx.fireGeometryChange({ featureId: 'missing' })
91
+ expect(map.fire).not.toHaveBeenCalled()
92
+ })
93
+
94
+ test('undoMoveVertex restores the previous position, guarding missing feature/segment', () => {
95
+ const { ctx, state } = createHarness()
96
+ ctx.undoMoveVertex({ ...state, featureId: 'missing' }, { vertexIndex: 0, previousPosition: [0, 0], featureId: 'missing' })
97
+ ctx.undoMoveVertex(state, { vertexIndex: 99, previousPosition: [0, 0], featureId: 'feat-1' })
98
+ state.selectedVertexIndex = 0
99
+ ctx.undoMoveVertex(state, { vertexIndex: 0, previousPosition: [3, 4], featureId: 'feat-1' })
100
+ expect(ctx.getVerticies('feat-1')[0]).toEqual([3, 4])
101
+
102
+ // Skips the touch target update when the selected vertex is gone
103
+ ctx.undoMoveVertex({ ...state, selectedVertexIndex: 99 }, { vertexIndex: 0, previousPosition: [1, 1], featureId: 'feat-1' })
104
+ expect(ctx.getVerticies('feat-1')[0]).toEqual([1, 1])
105
+ })
106
+
107
+ test('undoInsertVertex removes the inserted vertex and clears the selection', () => {
108
+ const { ctx, state, api } = createHarness()
109
+ ctx.undoInsertVertex(state, { vertexIndex: 1, featureId: 'feat-1' })
110
+ expect(ctx.getVerticies('feat-1')).toHaveLength(3)
111
+ expect(api.changeMode).toHaveBeenCalledWith('edit_vertex', expect.objectContaining({ selectedVertexIndex: -1 }))
112
+
113
+ // Guards a missing feature and an out-of-range index
114
+ ctx.undoInsertVertex({ ...state, featureId: 'missing' }, { vertexIndex: 0, featureId: 'missing' })
115
+ ctx.undoInsertVertex(state, { vertexIndex: 999, featureId: 'feat-1' })
116
+ expect(ctx.getVerticies('feat-1')).toHaveLength(3)
117
+ })
118
+
119
+ test('undoDeleteVertex re-inserts, handling boundary, fallback and missing feature', () => {
120
+ const { ctx, state } = createHarness()
121
+ ctx.undoDeleteVertex({ ...state, featureId: 'missing' }, { vertexIndex: 0, position: [1, 1], featureId: 'missing' })
122
+ // Fallback: index far beyond any segment boundary → no result → no-op
123
+ ctx.undoDeleteVertex(state, { vertexIndex: 999, position: [1, 1], featureId: 'feat-1' })
124
+ expect(ctx.getVerticies('feat-1')).toHaveLength(4)
125
+ // Success: re-insert at index 1
126
+ ctx.undoDeleteVertex(state, { vertexIndex: 1, position: [1, 1], featureId: 'feat-1' })
127
+ expect(ctx.getVerticies('feat-1')).toHaveLength(5)
128
+ // Boundary: index === segment.start + segment.length
129
+ const seg = getRingSegments(ctx.getFeature('feat-1'))[0]
130
+ ctx.undoDeleteVertex(state, { vertexIndex: seg.start + seg.length, position: [2, 2], featureId: 'feat-1' })
131
+ expect(ctx.getVerticies('feat-1')).toHaveLength(6)
132
+ })
133
+ })