@defra/interactive-map 0.0.40-alpha → 0.0.41-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 (351) hide show
  1. package/dist/css/index.css +1 -1
  2. package/dist/esm/im-core.js +1 -1
  3. package/dist/esm/im-shell.js +1 -1
  4. package/dist/umd/im-core.js +1 -1
  5. package/dist/umd/index.js +1 -1
  6. package/docs/api/control-definition.md +9 -1
  7. package/docs/api/slots.md +26 -5
  8. package/docs/assets/images/slot-map.svg +61 -262
  9. package/docs/examples/draw-tools.mdx +3 -3
  10. package/docs/plugins/draw.md +79 -16
  11. package/docs/plugins/search.md +1 -1
  12. package/govuk-prototype-kit.config.json +2 -0
  13. package/jest.setup.js +5 -0
  14. package/package.json +9 -1
  15. package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
  16. package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
  17. package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
  18. package/plugins/beta/draw-ol/dist/esm/im-draw-ol-plugin.js +1 -1
  19. package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
  20. package/plugins/beta/map-styles/dist/umd/index.js +1 -1
  21. package/plugins/datasets/dist/css/index.css +1 -1
  22. package/plugins/datasets/dist/esm/esriLayerAdapter.js +1 -1
  23. package/plugins/datasets/dist/esm/im-datasets-ml-adapter.js +1 -1
  24. package/plugins/datasets/dist/esm/im-datasets-plugin.js +1 -1
  25. package/plugins/datasets/dist/umd/im-datasets-esri-adapter.js +1 -1
  26. package/plugins/datasets/dist/umd/im-datasets-ml-adapter.js +1 -1
  27. package/plugins/datasets/dist/umd/im-datasets-plugin.js +1 -1
  28. package/plugins/datasets/dist/umd/index.js +1 -1
  29. package/plugins/datasets/src/adapters/esri/esriLayerAdapter.test.js +175 -0
  30. package/plugins/datasets/src/adapters/esri/registry/esriDataset.test.js +55 -0
  31. package/plugins/datasets/src/adapters/loadLayerAdapter.test.js +61 -0
  32. package/plugins/datasets/src/adapters/maplibre/maplibreLayerAdapter.test.js +14 -0
  33. package/plugins/datasets/src/api/setOpacity.test.js +9 -0
  34. package/plugins/datasets/src/components/LayersMenu/LayersMenu.jsx +10 -25
  35. package/plugins/datasets/src/components/LayersMenu/LayersMenu.test.jsx +0 -42
  36. package/plugins/datasets/src/datasets.scss +0 -1
  37. package/plugins/datasets/src/fetch/createDynamicSource.js +135 -129
  38. package/plugins/datasets/src/fetch/createDynamicSource.test.js +318 -0
  39. package/plugins/datasets/src/fetch/fetchGeoJSON.test.js +83 -0
  40. package/plugins/datasets/src/index.test.js +36 -0
  41. package/plugins/datasets/src/initialise/DatasetsInit.jsx +17 -7
  42. package/plugins/datasets/src/initialise/DatasetsInit.test.jsx +262 -0
  43. package/plugins/datasets/src/initialise/initialiseDatasets.js +16 -4
  44. package/plugins/datasets/src/initialise/initialiseDatasets.test.js +42 -0
  45. package/plugins/datasets/src/manifest.js +0 -38
  46. package/plugins/datasets/src/manifest.test.js +49 -0
  47. package/plugins/datasets/src/reducers/datasetsToMenu.test.js +54 -1
  48. package/plugins/datasets/src/reducers/pluginState.js +3 -16
  49. package/plugins/datasets/src/reducers/pluginState.test.js +23 -0
  50. package/plugins/datasets/src/registry/dataset.js +13 -1
  51. package/plugins/datasets/src/registry/dataset.test.js +61 -0
  52. package/plugins/datasets/src/registry/datasetRegistry.js +12 -6
  53. package/plugins/datasets/src/registry/datasetRegistry.test.js +16 -4
  54. package/plugins/datasets/src/registry/isVisibleWhen.js +6 -3
  55. package/plugins/datasets/src/registry/isVisibleWhen.test.js +11 -1
  56. package/plugins/datasets/src/utils/bbox.test.js +125 -0
  57. package/plugins/draw/dist/esm/im-draw-ml-adapter.js +1 -1
  58. package/plugins/draw/dist/esm/im-draw-ol-adapter.js +1 -1
  59. package/plugins/draw/dist/esm/im-draw-plugin.js +1 -1
  60. package/plugins/draw/dist/esm/index.js +1 -1
  61. package/plugins/draw/dist/umd/im-draw-ml-adapter.js +2 -1
  62. package/plugins/draw/dist/umd/im-draw-ml-adapter.js.LICENSE.txt +1 -0
  63. package/plugins/draw/dist/umd/im-draw-ol-adapter.js +1 -1
  64. package/plugins/draw/dist/umd/im-draw-plugin.js +1 -1
  65. package/plugins/draw/dist/umd/index.js +1 -1
  66. package/plugins/draw/src/DrawInit.jsx +10 -2
  67. package/plugins/draw/src/DrawInit.test.jsx +51 -10
  68. package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.js +91 -11
  69. package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.test.js +246 -4
  70. package/plugins/draw/src/adapters/maplibre/mapboxDraw.js +109 -14
  71. package/plugins/draw/src/adapters/maplibre/mapboxDraw.test.js +174 -11
  72. package/plugins/draw/src/adapters/maplibre/modes/drawMode/clickHandlers.test.js +12 -0
  73. package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.js +9 -1
  74. package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.test.js +24 -3
  75. package/plugins/draw/src/adapters/maplibre/modes/drawPointMode.js +194 -0
  76. package/plugins/draw/src/adapters/maplibre/modes/drawPointMode.test.js +457 -0
  77. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/__helpers__/harness.js +102 -0
  78. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/keyboardHandlers.js +82 -0
  79. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/keyboardHandlers.test.js +83 -0
  80. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointOperations.js +55 -0
  81. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointOperations.test.js +62 -0
  82. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointerHandlers.js +138 -0
  83. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointerHandlers.test.js +197 -0
  84. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/touchHandlers.js +92 -0
  85. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/touchHandlers.test.js +101 -0
  86. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/undoHandlers.js +46 -0
  87. package/plugins/draw/src/adapters/maplibre/modes/editPointMode/undoHandlers.test.js +42 -0
  88. package/plugins/draw/src/adapters/maplibre/modes/editPointMode.js +151 -0
  89. package/plugins/draw/src/adapters/maplibre/modes/editPointMode.test.js +156 -0
  90. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.js +4 -27
  91. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.test.js +24 -39
  92. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/pointerHandlers.js +4 -5
  93. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.js +20 -23
  94. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.test.js +25 -5
  95. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.js +6 -36
  96. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.test.js +7 -49
  97. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.js +3 -53
  98. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.test.js +8 -33
  99. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.js +29 -72
  100. package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.test.js +10 -3
  101. package/plugins/draw/src/adapters/maplibre/pointSymbolImages.js +158 -0
  102. package/plugins/draw/src/adapters/maplibre/pointSymbolImages.test.js +314 -0
  103. package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.js +6 -1
  104. package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.test.js +21 -0
  105. package/plugins/draw/src/adapters/maplibre/snap/snapInstance.js +6 -2
  106. package/plugins/draw/src/adapters/maplibre/snap/snapInstance.test.js +12 -0
  107. package/plugins/draw/src/adapters/maplibre/styles.js +38 -11
  108. package/plugins/draw/src/adapters/maplibre/styles.test.js +39 -0
  109. package/plugins/draw/src/adapters/maplibre/utils/editModeEvents.js +79 -0
  110. package/plugins/draw/src/adapters/maplibre/utils/editModeEvents.test.js +135 -0
  111. package/plugins/draw/src/adapters/maplibre/utils/geometryValidation.js +42 -0
  112. package/plugins/draw/src/adapters/maplibre/utils/geometryValidation.test.js +85 -0
  113. package/plugins/draw/src/adapters/maplibre/utils/keyboardShortcuts.js +34 -0
  114. package/plugins/draw/src/adapters/maplibre/utils/keyboardShortcuts.test.js +78 -0
  115. package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.js +16 -3
  116. package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.test.js +21 -3
  117. package/plugins/draw/src/adapters/maplibre/utils/snapMovement.js +59 -0
  118. package/plugins/draw/src/adapters/maplibre/utils/snapMovement.test.js +73 -0
  119. package/plugins/draw/src/adapters/maplibre/utils/touchDragMath.js +33 -0
  120. package/plugins/draw/src/adapters/maplibre/utils/touchDragMath.test.js +70 -0
  121. package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.js +27 -2
  122. package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.test.js +93 -0
  123. package/plugins/draw/src/adapters/openlayers/__helpers__/harness.js +9 -0
  124. package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.js +19 -1
  125. package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.test.js +34 -1
  126. package/plugins/draw/src/adapters/openlayers/core/styles.js +73 -1
  127. package/plugins/draw/src/adapters/openlayers/core/styles.test.js +122 -3
  128. package/plugins/draw/src/adapters/openlayers/draw/DrawMode.test.js +12 -0
  129. package/plugins/draw/src/adapters/openlayers/draw/drawInput.js +8 -2
  130. package/plugins/draw/src/adapters/openlayers/edit/EditMode.test.js +31 -0
  131. package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.js +9 -5
  132. package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.test.js +33 -0
  133. package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.js +5 -3
  134. package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.test.js +11 -0
  135. package/plugins/draw/src/adapters/openlayers/edit/nudge.js +10 -3
  136. package/plugins/draw/src/adapters/openlayers/edit/nudge.test.js +24 -1
  137. package/plugins/draw/src/adapters/openlayers/edit/touchHandler.js +18 -13
  138. package/plugins/draw/src/adapters/openlayers/edit/touchHandler.test.js +46 -1
  139. package/plugins/draw/src/adapters/openlayers/olDraw.js +11 -4
  140. package/plugins/draw/src/adapters/openlayers/olDraw.test.js +48 -6
  141. package/plugins/draw/src/adapters/openlayers/point/drawPointMode.js +156 -0
  142. package/plugins/draw/src/adapters/openlayers/point/drawPointMode.test.js +246 -0
  143. package/plugins/draw/src/adapters/openlayers/point/editPointMode.js +267 -0
  144. package/plugins/draw/src/adapters/openlayers/point/editPointMode.test.js +327 -0
  145. package/plugins/draw/src/adapters/openlayers/point/pointDragInteraction.js +71 -0
  146. package/plugins/draw/src/adapters/openlayers/point/pointOps.js +34 -0
  147. package/plugins/draw/src/adapters/openlayers/point/pointOps.test.js +32 -0
  148. package/plugins/draw/src/adapters/openlayers/point/pointSelectionState.js +82 -0
  149. package/plugins/draw/src/adapters/openlayers/point/pointSelectionState.test.js +107 -0
  150. package/plugins/draw/src/adapters/openlayers/point/pointSymbolImages.js +83 -0
  151. package/plugins/draw/src/adapters/openlayers/point/pointSymbolImages.test.js +214 -0
  152. package/plugins/draw/src/adapters/openlayers/snap/snapIndicator.js +3 -1
  153. package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.js +6 -9
  154. package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.test.js +15 -4
  155. package/plugins/draw/src/adapters/openlayers/utils/olCoords.js +1 -1
  156. package/plugins/draw/src/api/addFeature.js +16 -1
  157. package/plugins/draw/src/api/addFeature.test.js +62 -5
  158. package/plugins/draw/src/api/editFeature.js +7 -4
  159. package/plugins/draw/src/api/editFeature.test.js +16 -0
  160. package/plugins/draw/src/api/newPoint.js +42 -0
  161. package/plugins/draw/src/api/newPoint.test.js +117 -0
  162. package/plugins/draw/src/api/setStyle.js +43 -0
  163. package/plugins/draw/src/api/setStyle.test.js +75 -0
  164. package/plugins/draw/src/events.js +17 -10
  165. package/plugins/draw/src/events.test.js +14 -0
  166. package/plugins/draw/src/manifest.js +18 -6
  167. package/plugins/draw/src/manifest.test.js +32 -1
  168. package/plugins/draw/src/reducer.test.js +7 -0
  169. package/plugins/draw/src/utils/stripInternalProperties.js +19 -0
  170. package/plugins/draw/src/utils/stripInternalProperties.test.js +50 -0
  171. package/plugins/draw/src/utils/symbolKeys.js +5 -0
  172. package/plugins/draw/src/validation/liveDrawChecks.test.js +54 -0
  173. package/plugins/draw/src/validation/liveStroke.test.js +64 -1
  174. package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
  175. package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
  176. package/plugins/interact/dist/umd/index.js +1 -1
  177. package/plugins/interact/src/hooks/useHighlightSync.js +33 -12
  178. package/plugins/interact/src/hooks/useHighlightSync.test.js +96 -25
  179. package/plugins/interact/src/utils/featureQueries.js +18 -0
  180. package/plugins/interact/src/utils/featureQueries.test.js +15 -0
  181. package/plugins/interact/src/utils/spatial.js +9 -3
  182. package/plugins/interact/src/utils/spatial.test.js +12 -1
  183. package/plugins/map-key/dist/css/index.css +62 -0
  184. package/plugins/map-key/dist/esm/im-map-key-plugin.js +1 -0
  185. package/plugins/map-key/dist/esm/index.js +1 -0
  186. package/plugins/map-key/dist/umd/im-map-key-plugin.js +2 -0
  187. package/plugins/map-key/dist/umd/im-map-key-plugin.js.LICENSE.txt +1 -0
  188. package/plugins/map-key/dist/umd/index.js +2 -0
  189. package/plugins/map-key/dist/umd/index.js.LICENSE.txt +1 -0
  190. package/plugins/map-key/package.json +28 -0
  191. package/plugins/map-key/src/components/Key/EmptyKey.jsx +7 -0
  192. package/plugins/{datasets → map-key}/src/components/Key/EmptyKey.test.jsx +2 -2
  193. package/plugins/map-key/src/components/Key/Key.jsx +41 -0
  194. package/plugins/{datasets → map-key}/src/components/Key/Key.module.scss +15 -14
  195. package/plugins/map-key/src/components/Key/Key.test.jsx +73 -0
  196. package/plugins/map-key/src/components/Key/KeyGroupItem.jsx +16 -0
  197. package/plugins/map-key/src/components/Key/KeyGroupItem.test.jsx +53 -0
  198. package/plugins/map-key/src/components/Key/KeyItem.jsx +29 -0
  199. package/plugins/map-key/src/components/Key/KeyItem.test.jsx +67 -0
  200. package/plugins/map-key/src/components/Key/KeySvg.jsx +42 -0
  201. package/plugins/{datasets → map-key}/src/components/Key/KeySvg.test.jsx +14 -12
  202. package/plugins/map-key/src/components/Key/KeySvgLine.jsx +28 -0
  203. package/plugins/{datasets → map-key}/src/components/Key/KeySvgLine.test.jsx +1 -1
  204. package/plugins/map-key/src/components/Key/KeySvgPattern.jsx +23 -0
  205. package/plugins/{datasets → map-key}/src/components/Key/KeySvgPattern.test.jsx +2 -2
  206. package/plugins/map-key/src/components/Key/KeySvgRect.jsx +33 -0
  207. package/plugins/{datasets → map-key}/src/components/Key/KeySvgRect.test.jsx +1 -1
  208. package/plugins/map-key/src/components/Key/KeySvgSymbol.jsx +27 -0
  209. package/plugins/{datasets → map-key}/src/components/Key/KeySvgSymbol.test.jsx +5 -6
  210. package/plugins/map-key/src/components/Key/MapKey.jsx +50 -0
  211. package/plugins/map-key/src/components/Key/MapKey.test.jsx +130 -0
  212. package/plugins/{datasets → map-key}/src/components/Key/svgProperties.js +2 -2
  213. package/plugins/{datasets → map-key}/src/components/Key/svgProperties.test.js +2 -2
  214. package/plugins/map-key/src/index.js +13 -0
  215. package/plugins/map-key/src/index.test.js +31 -0
  216. package/plugins/map-key/src/initialise/MapKeyInit.jsx +15 -0
  217. package/plugins/map-key/src/initialise/MapKeyInit.test.jsx +39 -0
  218. package/plugins/map-key/src/manifest.js +30 -0
  219. package/plugins/map-key/src/manifest.test.js +63 -0
  220. package/plugins/map-key/src/mapKey.scss +4 -0
  221. package/plugins/map-key/src/registry/getDatasetRegistry.js +6 -0
  222. package/plugins/map-key/src/registry/getDatasetRegistry.test.js +23 -0
  223. package/plugins/map-key/src/registry/index.js +3 -0
  224. package/plugins/map-key/src/registry/index.test.js +15 -0
  225. package/plugins/menu/dist/css/index.css +80 -0
  226. package/plugins/menu/dist/esm/im-menu-plugin.js +1 -0
  227. package/plugins/menu/dist/esm/index.js +1 -0
  228. package/plugins/menu/dist/umd/im-menu-plugin.js +1 -0
  229. package/plugins/menu/dist/umd/index.js +2 -0
  230. package/plugins/menu/dist/umd/index.js.LICENSE.txt +1 -0
  231. package/plugins/menu/package.json +28 -0
  232. package/plugins/menu/src/components/Menu/CheckboxGroupWrapper.jsx +21 -0
  233. package/plugins/menu/src/components/Menu/GroupLegend.jsx +17 -0
  234. package/plugins/menu/src/components/Menu/GroupLegend.test.jsx +68 -0
  235. package/plugins/menu/src/components/Menu/Menu.jsx +19 -0
  236. package/plugins/menu/src/components/Menu/Menu.module.scss +90 -0
  237. package/plugins/menu/src/components/Menu/Menu.test.jsx +161 -0
  238. package/plugins/menu/src/components/Menu/MenuCheckbox.jsx +35 -0
  239. package/plugins/menu/src/components/Menu/MenuCheckbox.test.jsx +93 -0
  240. package/plugins/menu/src/components/Menu/MenuGroup.jsx +11 -0
  241. package/plugins/{datasets/src/components/LayersMenu/LayersMenuRadio.jsx → menu/src/components/Menu/MenuRadio.jsx} +6 -4
  242. package/plugins/{datasets/src/components/LayersMenu/LayersMenuRadio.test.jsx → menu/src/components/Menu/MenuRadio.test.jsx} +18 -18
  243. package/plugins/menu/src/components/Menu/RadioGroupWrapper.jsx +32 -0
  244. package/plugins/{datasets/src/components/LayersMenu/LayersRadioGroupWrapper.test.jsx → menu/src/components/Menu/RadioGroupWrapper.test.jsx} +21 -21
  245. package/plugins/menu/src/components/Menu/useIdPrefix.jsx +4 -0
  246. package/plugins/menu/src/index.js +13 -0
  247. package/plugins/menu/src/index.test.js +31 -0
  248. package/plugins/menu/src/initialise/MenuInit.jsx +24 -0
  249. package/plugins/menu/src/initialise/MenuInit.test.jsx +50 -0
  250. package/plugins/menu/src/initialise/createMenu.js +10 -0
  251. package/plugins/menu/src/initialise/createMenu.test.js +37 -0
  252. package/plugins/menu/src/manifest.js +35 -0
  253. package/plugins/menu/src/manifest.test.js +63 -0
  254. package/plugins/menu/src/menu.scss +4 -0
  255. package/plugins/menu/src/reducers/menuStateReducer.test.js +38 -0
  256. package/plugins/menu/src/reducers/pluginState.js +35 -0
  257. package/plugins/menu/src/reducers/pluginState.test.js +57 -0
  258. package/plugins/menu/src/registry/getDatasetRegistry.js +9 -0
  259. package/plugins/menu/src/registry/getDatasetRegistry.test.js +23 -0
  260. package/plugins/menu/src/registry/index.js +3 -0
  261. package/plugins/menu/src/registry/index.test.js +15 -0
  262. package/plugins/menu/src/registry/isVisibleWhen.js +43 -0
  263. package/plugins/menu/src/registry/isVisibleWhen.test.js +48 -0
  264. package/plugins/search/dist/css/index.css +1 -1
  265. package/plugins/search/dist/esm/im-search-plugin.js +1 -1
  266. package/plugins/search/dist/esm/index.js +1 -1
  267. package/plugins/search/dist/umd/im-search-plugin.js +1 -1
  268. package/plugins/search/dist/umd/index.js +1 -1
  269. package/plugins/search/src/Search.jsx +2 -3
  270. package/plugins/search/src/components/Form/Form.module.scss +9 -20
  271. package/plugins/search/src/index.js +0 -1
  272. package/plugins/search/src/index.test.js +2 -4
  273. package/plugins/search/src/manifest.js +1 -1
  274. package/plugins/search/src/search.scss +17 -16
  275. package/providers/beta/openlayers/dist/esm/im-openlayers-provider.js +1 -1
  276. package/providers/beta/openlayers/dist/umd/im-openlayers-provider.js +1 -1
  277. package/providers/beta/openlayers/dist/umd/index.js +1 -1
  278. package/providers/beta/openlayers/src/openlayersProvider.js +4 -1
  279. package/providers/beta/openlayers/src/openlayersProvider.test.js +18 -0
  280. package/providers/beta/openlayers/src/utils/highlightFeatures.js +52 -5
  281. package/providers/beta/openlayers/src/utils/highlightFeatures.test.js +176 -0
  282. package/providers/beta/openlayers/src/utils/symbolImages.js +40 -0
  283. package/providers/beta/openlayers/src/utils/symbolImages.test.js +58 -0
  284. package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
  285. package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
  286. package/providers/maplibre/dist/umd/index.js +1 -1
  287. package/providers/maplibre/src/utils/drawLayerBuckets.js +14 -0
  288. package/providers/maplibre/src/utils/drawLayerBuckets.test.js +18 -0
  289. package/providers/maplibre/src/utils/highlightFeatures.js +80 -55
  290. package/providers/maplibre/src/utils/highlightFeatures.test.js +156 -17
  291. package/providers/maplibre/src/utils/hoverCursor.js +30 -14
  292. package/providers/maplibre/src/utils/hoverCursor.test.js +35 -0
  293. package/providers/maplibre/src/utils/patternImages.js +1 -1
  294. package/providers/maplibre/src/utils/symbolImages.js +41 -3
  295. package/providers/maplibre/src/utils/symbolImages.test.js +40 -6
  296. package/rollup.esm.mjs +11 -1
  297. package/src/App/components/Actions/Actions.test.jsx +47 -0
  298. package/src/App/components/Hints/Hints.test.jsx +13 -0
  299. package/src/App/components/MoveControls/MoveControls.module.scss +41 -10
  300. package/src/App/components/Panel/Panel.jsx +18 -3
  301. package/src/App/components/Panel/Panel.module.scss +18 -8
  302. package/src/App/components/Panel/Panel.test.jsx +34 -0
  303. package/src/App/components/Tooltip/Tooltip.test.jsx +12 -14
  304. package/src/App/hooks/useFeatureItems.js +16 -7
  305. package/src/App/hooks/useFeatureItems.test.js +57 -0
  306. package/src/App/hooks/useLayoutMeasurements.js +95 -84
  307. package/src/App/hooks/useLayoutMeasurements.test.js +61 -1
  308. package/src/App/layout/Layout.jsx +9 -10
  309. package/src/App/layout/Layout.test.jsx +2 -0
  310. package/src/App/layout/layout.module.scss +31 -17
  311. package/src/App/registry/pluginRegistry.js +1 -1
  312. package/src/App/registry/pluginRegistry.test.js +12 -0
  313. package/src/App/renderer/HtmlElementHost.jsx +18 -5
  314. package/src/App/renderer/HtmlElementHost.test.jsx +54 -0
  315. package/src/App/renderer/mapButtons.js +28 -12
  316. package/src/App/renderer/mapButtons.test.js +17 -4
  317. package/src/App/renderer/mapControls.js +3 -1
  318. package/src/App/renderer/mapControls.test.js +16 -0
  319. package/src/App/renderer/mapPanels.js +44 -9
  320. package/src/App/renderer/mapPanels.test.js +51 -2
  321. package/src/App/renderer/orderItems.js +28 -0
  322. package/src/App/renderer/orderItems.test.js +50 -0
  323. package/src/App/renderer/slotAggregator.js +2 -18
  324. package/src/App/renderer/slotHelpers.js +3 -1
  325. package/src/App/renderer/slotHelpers.test.js +5 -0
  326. package/src/App/renderer/slots.js +3 -0
  327. package/src/App/store/AppProvider.jsx +1 -0
  328. package/src/App/store/appDispatchMiddleware.js +1 -1
  329. package/src/App/store/appDispatchMiddleware.test.js +8 -0
  330. package/src/config/events.js +2 -0
  331. package/src/scss/settings/_colors.scss +1 -0
  332. package/src/scss/settings/_dimensions.scss +2 -3
  333. package/src/scss/settings/_transition.scss +1 -1
  334. package/src/services/eventBus.js +44 -0
  335. package/src/services/eventBus.test.js +83 -0
  336. package/src/services/symbolRegistry.js +1 -1
  337. package/src/types.js +11 -1
  338. package/webpack.umd.mjs +2 -0
  339. package/plugins/datasets/src/components/Key/EmptyKey.jsx +0 -7
  340. package/plugins/datasets/src/components/Key/Key.jsx +0 -50
  341. package/plugins/datasets/src/components/Key/KeyGroupItem.jsx +0 -18
  342. package/plugins/datasets/src/components/Key/KeyItem.jsx +0 -23
  343. package/plugins/datasets/src/components/Key/KeySvg.jsx +0 -22
  344. package/plugins/datasets/src/components/Key/KeySvgLine.jsx +0 -19
  345. package/plugins/datasets/src/components/Key/KeySvgPattern.jsx +0 -13
  346. package/plugins/datasets/src/components/Key/KeySvgRect.jsx +0 -22
  347. package/plugins/datasets/src/components/Key/KeySvgSymbol.jsx +0 -16
  348. package/plugins/datasets/src/components/LayersMenu/LayersRadioGroupWrapper.jsx +0 -41
  349. /package/plugins/{datasets → menu}/src/reducers/menuStateReducer.js +0 -0
  350. /package/{providers/maplibre/src → src}/utils/rasteriseToImageData.js +0 -0
  351. /package/{providers/maplibre/src → src}/utils/rasteriseToImageData.test.js +0 -0
@@ -3,7 +3,7 @@ import {
3
3
  datasetsWithGroups, expectedDatasetsMenuConfigWithGroups
4
4
  } from './__data__/demoDatasets.js'
5
5
 
6
- import { datasetsToMenu } from './datasetsToMenu'
6
+ import { datasetsToMenu, addDatasetToMenu, removeDatasetsFromMenu } from './datasetsToMenu'
7
7
 
8
8
  describe('datasetsToMenu', () => {
9
9
  describe('Missing or empty datasets', () => {
@@ -80,5 +80,58 @@ describe('datasetsToMenu', () => {
80
80
  const result = datasetsToMenu({ datasets: [sublayerDataset(true, false)] })
81
81
  expect(result).toEqual([{ id: 'test', items: [{ id: 'test', label: 'Test' }], type: 'checkbox', visibleWhen: true }])
82
82
  })
83
+
84
+ it('skips a parent with showInMenu:false when no sublayers are visible', () => {
85
+ // Contrived: a getter that returns true on the first access (outer filter's `some`)
86
+ // and false on subsequent accesses (inner filter), hitting the early-return guard.
87
+ let calls = 0
88
+ const dataset = {
89
+ id: 'tricky',
90
+ label: 'Tricky',
91
+ showInMenu: false,
92
+ sublayers: [{ id: 'tricky-a', label: 'A', get showInMenu () { return calls++ === 0 } }]
93
+ }
94
+ const result = datasetsToMenu({ datasets: [dataset] })
95
+ expect(result).toEqual([])
96
+ })
97
+ })
98
+ })
99
+
100
+ describe('addDatasetToMenu', () => {
101
+ it('appends to an existing menu group with matching groupLabel', () => {
102
+ const state = {
103
+ menu: [{ id: 'Layers', groupLabel: 'Layers', type: 'checkbox', visibleWhen: true, items: [{ id: 'roads', label: 'Roads' }] }]
104
+ }
105
+ const dataset = { id: 'rivers', label: 'Rivers', showInMenu: true, groupLabel: 'Layers' }
106
+ const result = addDatasetToMenu(state, dataset)
107
+ expect(result[0].items).toHaveLength(2)
108
+ expect(result[0].items[1]).toEqual({ id: 'rivers', label: 'Rivers' })
109
+ })
110
+
111
+ it('pushes a new group when no matching groupLabel exists', () => {
112
+ const state = { menu: [] }
113
+ const dataset = { id: 'parks', label: 'Parks', showInMenu: true }
114
+ const result = addDatasetToMenu(state, dataset)
115
+ expect(result).toHaveLength(1)
116
+ expect(result[0].items[0].id).toBe('parks')
117
+ })
118
+ })
119
+
120
+ describe('removeDatasetsFromMenu', () => {
121
+ it('keeps groups that still have items after filtering', () => {
122
+ const menu = [
123
+ { id: 'Layers', items: [{ id: 'roads' }, { id: 'rivers' }] }
124
+ ]
125
+ const result = removeDatasetsFromMenu(menu, ['roads'])
126
+ expect(result).toHaveLength(1)
127
+ expect(result[0].items).toEqual([{ id: 'rivers' }])
128
+ })
129
+
130
+ it('removes groups that are left with no items', () => {
131
+ const menu = [
132
+ { id: 'Layers', items: [{ id: 'roads' }] }
133
+ ]
134
+ const result = removeDatasetsFromMenu(menu, ['roads'])
135
+ expect(result).toHaveLength(0)
83
136
  })
84
137
  })
@@ -1,5 +1,4 @@
1
1
  import { addDatasetToMenu, removeDatasetsFromMenu } from './datasetsToMenu.js'
2
- import { buildMenuState } from './menuStateReducer.js'
3
2
  import { mappedDatasetsReducer } from './mappedDatasetsReducer.js'
4
3
  import { logger } from '../../../../src/services/logger.js'
5
4
 
@@ -19,8 +18,7 @@ const initialState = {
19
18
  mappedDatasets: {},
20
19
  orderedDatasets: [],
21
20
  actionsArray: [],
22
- menu: [],
23
- menuState: {}
21
+ menu: []
24
22
  }
25
23
 
26
24
  const validateDatasetExists = (state, datasetId, prefix, suffix = 'not found') => {
@@ -46,21 +44,11 @@ const setGlobalState = (state, payload) => {
46
44
  })
47
45
  }
48
46
 
49
- const updateMenuState = (state, payload) => {
50
- return addAction('applyGlobalVisibility', [], {
51
- ...state,
52
- menuState: { ...state.menuState, ...payload }
53
- })
54
- }
55
-
56
47
  const setMenu = (state, payload) => {
57
48
  const { menu } = payload
58
- // build the initial menuState for radios from the menu
59
- const menuState = buildMenuState(menu)
60
49
  return {
61
50
  ...state,
62
- menu,
63
- menuState
51
+ menu
64
52
  }
65
53
  }
66
54
 
@@ -223,8 +211,7 @@ const actions = {
223
211
  SHOW_FEATURES: showFeatures,
224
212
  REMOVE_ADAPTER_ACTIONS: removeAdapterActions,
225
213
  SET_GLOBAL_STATE: setGlobalState,
226
- SET_MENU: setMenu,
227
- UPDATE_MENU_STATE: updateMenuState
214
+ SET_MENU: setMenu
228
215
  }
229
216
 
230
217
  export {
@@ -172,6 +172,29 @@ describe('SET_DATASET_VISIBILITY', () => {
172
172
  expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("'unknown'"))
173
173
  expect(result).toBe(state)
174
174
  })
175
+
176
+ it('returns unchanged state when visibility already matches', () => {
177
+ const state = makeStateWithDataset('roads')
178
+ const result = actions.SET_DATASET_VISIBILITY(state, { datasetId: 'roads', visible: true })
179
+ expect(result).toBe(state)
180
+ })
181
+ })
182
+
183
+ // ─── SET_MENU ────────────────────────────────────────────────────────────────
184
+
185
+ describe('SET_MENU', () => {
186
+ it('sets menu from payload', () => {
187
+ const state = makeState()
188
+ const menu = [{ id: 'layers', items: [] }]
189
+ const result = actions.SET_MENU(state, { menu })
190
+ expect(result.menu).toBe(menu)
191
+ })
192
+
193
+ it('does not add to actionsArray', () => {
194
+ const state = makeState()
195
+ const result = actions.SET_MENU(state, { menu: [] })
196
+ expect(result.actionsArray).toEqual([])
197
+ })
175
198
  })
176
199
 
177
200
  // ─── SET_GLOBAL_VISIBILITY ────────────────────────────────────────────────────
@@ -108,7 +108,7 @@ export class Dataset {
108
108
  }
109
109
 
110
110
  get keyVisibility () {
111
- return this.visibility === 'visible' && this.showInKey
111
+ return this.showInKey && this.visibility === 'visible'
112
112
  }
113
113
 
114
114
  get symbolAnchor () {
@@ -201,4 +201,16 @@ export class Dataset {
201
201
  }
202
202
  return styleSymbols
203
203
  }
204
+
205
+ get keyDefinition () {
206
+ return {
207
+ id: this.id,
208
+ type: 'dataset',
209
+ label: this.label,
210
+ hasSymbol: this.hasSymbol,
211
+ hasPattern: this.hasPattern,
212
+ style: this.style,
213
+ symbolDescription: this.symbolDescription
214
+ }
215
+ }
204
216
  }
@@ -494,4 +494,65 @@ describe('Dataset class', () => {
494
494
  expect(new Dataset(childDef).sourceLayer).toBe('parent_layer')
495
495
  })
496
496
  })
497
+
498
+ describe('isHiddenByInheritance', () => {
499
+ it('returns true for a sublayer whose parent is not visible', () => {
500
+ const parentDef = { id: 'parent', visible: false, style: {} }
501
+ const childDef = { id: 'child', parentId: 'parent', visible: true, style: {} }
502
+ datasetRegistry.attach({ parent: parentDef, child: childDef })
503
+ expect(new Dataset(childDef).isHiddenByInheritance).toBe(true)
504
+ })
505
+
506
+ it('returns false for a sublayer whose parent is visible', () => {
507
+ const parentDef = { id: 'parent', visible: true, style: {} }
508
+ const childDef = { id: 'child', parentId: 'parent', visible: true, style: {} }
509
+ datasetRegistry.attach({ parent: parentDef, child: childDef })
510
+ expect(new Dataset(childDef).isHiddenByInheritance).toBe(false)
511
+ })
512
+
513
+ it('returns false for a top-level dataset when global visibility is true', () => {
514
+ const dataset = new Dataset({ id: 'top', visible: true, style: {} })
515
+ expect(dataset.isHiddenByInheritance).toBe(false)
516
+ })
517
+ })
518
+
519
+ describe('isLocallyVisible', () => {
520
+ it('returns the raw visible flag from the definition when not visible', () => {
521
+ const dataset = new Dataset({ visible: false })
522
+ expect(dataset.isLocallyVisible).toBe(false)
523
+ })
524
+
525
+ it('returns undefined when visible is not set and dataset is not visible', () => {
526
+ const dataset = new Dataset({})
527
+ expect(dataset.isLocallyVisible).toBeUndefined()
528
+ })
529
+
530
+ it('returns true when isVisible is truthy (subclass override scenario)', () => {
531
+ const dataset = new Dataset({ visible: false })
532
+ Object.defineProperty(dataset, 'isVisible', { get: () => true, configurable: true })
533
+ expect(dataset.isLocallyVisible).toBe(true)
534
+ })
535
+ })
536
+
537
+ describe('dynamicGeoJSON getter', () => {
538
+ it('returns a DynamicGeoJson with idProperty set', () => {
539
+ const dataset = new Dataset({
540
+ id: 'parcels',
541
+ dynamicGeoJSON: { url: 'http://example.com', idProperty: 'parcel_id' }
542
+ })
543
+ const dg = dataset.dynamicGeoJSON
544
+ expect(dg.source.promoteId).toBe('parcel_id')
545
+ expect(dg.source.generateId).toBeUndefined()
546
+ })
547
+
548
+ it('returns a DynamicGeoJson with generateId when no idProperty', () => {
549
+ const dataset = new Dataset({
550
+ id: 'parcels',
551
+ dynamicGeoJSON: { url: 'http://example.com' }
552
+ })
553
+ const dg = dataset.dynamicGeoJSON
554
+ expect(dg.source.generateId).toBe(true)
555
+ expect(dg.source.promoteId).toBeUndefined()
556
+ })
557
+ })
497
558
  })
@@ -83,7 +83,12 @@ const datasetRegistry = {
83
83
  }, { patternConfigs: [], symbolConfigs: [] })
84
84
  },
85
85
 
86
- invalidateKeyItems () {
86
+ _lastMenuState: {},
87
+ invalidateKeyItemsOnMenuStateChange (menuState) {
88
+ if (menuState === this._lastMenuState) {
89
+ return
90
+ }
91
+ this._lastMenuState = menuState
87
92
  this._lastKeyItemsDatasets = null
88
93
  },
89
94
  _lastKeyItems: {},
@@ -101,8 +106,9 @@ const datasetRegistry = {
101
106
  }
102
107
  const groupObject = {
103
108
  type: 'group',
109
+ id: groupLabel.toLowerCase().replaceAll(/\s+/g, '-'),
104
110
  groupLabel,
105
- datasets: []
111
+ keyDefinitions: []
106
112
  }
107
113
  groups.set(groupLabel, groupObject)
108
114
  _items.push(groupObject)
@@ -110,22 +116,22 @@ const datasetRegistry = {
110
116
  }
111
117
 
112
118
  this.forEach((dataset) => {
113
- if (!(dataset.showInKey && dataset.keyVisibility)) {
119
+ if (!dataset.keyVisibility) {
114
120
  return
115
121
  }
116
122
  const isGroup = dataset.hasSublayers || dataset.groupLabel
117
123
  if (!isGroup) {
118
- _items.push({ type: 'flat', dataset })
124
+ _items.push({ type: 'flat', id: dataset.id, keyDefinition: dataset.keyDefinition })
119
125
  return
120
126
  }
121
127
 
122
128
  const groupLabel = dataset.groupLabel || dataset.label
123
129
  const groupObject = getOrCreateGroup(groupLabel)
124
130
  if (!dataset.hasSublayers) {
125
- groupObject.datasets.push(dataset)
131
+ groupObject.keyDefinitions.push(dataset.keyDefinition)
126
132
  }
127
133
  })
128
- const items = _items.filter((item) => item.type === 'flat' || Boolean(item.datasets?.length))
134
+ const items = _items.filter((item) => item.type === 'flat' || Boolean(item.keyDefinitions?.length))
129
135
  const hasGroups = items.some(item => item.type === 'group')
130
136
  this._lastKeyItems = { items, hasGroups }
131
137
  return this._lastKeyItems
@@ -148,6 +148,7 @@ describe('datasetRegistry', () => {
148
148
  const { mappedDatasets: singleHiddenSublayer, orderedDatasets } = mappedDatasetsReducer({
149
149
  datasets: [{
150
150
  id: 'single-sublayer-dataset',
151
+ label: 'Single Layer',
151
152
  showInKey: true,
152
153
  visible: true,
153
154
  style: {},
@@ -162,7 +163,7 @@ describe('datasetRegistry', () => {
162
163
  it('only includes visible sublayers in group items', () => {
163
164
  const { items } = datasetRegistry.keyItems()
164
165
  const landCoversItem = items.find(item => item.type === 'group' && item.groupLabel === 'Land covers')
165
- const sublayerIds = landCoversItem.datasets.map(s => s.id)
166
+ const sublayerIds = landCoversItem.keyDefinitions.map(s => s.id)
166
167
  expect(sublayerIds).not.toContain('land-covers-379') // land-covers-379 has visible: false
167
168
  })
168
169
 
@@ -188,7 +189,7 @@ describe('datasetRegistry', () => {
188
189
  datasetRegistry.attach(grouped, orderedDatasets)
189
190
  const { items } = datasetRegistry.keyItems()
190
191
  const groupItem = items.find(item => item.type === 'group' && item.groupLabel === 'Test group')
191
- const ids = groupItem.datasets.map(d => d.id)
192
+ const ids = groupItem.keyDefinitions.map(d => d.id)
192
193
  expect(ids).toContain('existing-fields')
193
194
  expect(ids).toContain('hedge-control')
194
195
  })
@@ -199,13 +200,24 @@ describe('datasetRegistry', () => {
199
200
  expect(result1).toBe(result2)
200
201
  })
201
202
 
202
- it('recalculates caches the cached result when invalidateKeyItems is called', () => {
203
+ it('recalculates the keyItems when invalidateKeyItemsOnMenuStateChange is called with new menuState', () => {
204
+ const menuState = { someMenuKey: 'someValue' }
205
+ datasetRegistry.invalidateKeyItemsOnMenuStateChange({})
203
206
  const result1 = datasetRegistry.keyItems()
204
- datasetRegistry.invalidateKeyItems()
207
+ datasetRegistry.invalidateKeyItemsOnMenuStateChange(menuState)
205
208
  const result2 = datasetRegistry.keyItems()
206
209
  expect(result1).not.toBe(result2)
207
210
  })
208
211
 
212
+ it('keeps the cached keyItems when invalidateKeyItemsOnMenuStateChange is called with the same menuState', () => {
213
+ const menuState = { someMenuKey: 'someOtherValue' }
214
+ datasetRegistry.invalidateKeyItemsOnMenuStateChange(menuState)
215
+ const result1 = datasetRegistry.keyItems()
216
+ datasetRegistry.invalidateKeyItemsOnMenuStateChange(menuState)
217
+ const result2 = datasetRegistry.keyItems()
218
+ expect(result1).toBe(result2)
219
+ })
220
+
209
221
  it('recomputes when the datasets ref changes', () => {
210
222
  const result1 = datasetRegistry.keyItems()
211
223
  datasetRegistry.attach({ simple: { id: 'simple', showInKey: true, visible: true, style: {} } }, ['simple'])
@@ -1,11 +1,14 @@
1
1
  import { datasetRegistry } from './datasetRegistry.js'
2
2
 
3
- let _menuState = {}
4
- export const setMenuState = (menuState) => { _menuState = menuState }
3
+ let _menuStateRef = {}
4
+ export const attachMenuStateRef = (menuStateRef) => {
5
+ _menuStateRef = menuStateRef
6
+ }
5
7
 
6
8
  const _isVisibleWhenMenuCheck = (menuVisibleWhen) => {
9
+ const menuState = _menuStateRef?.current?.menuState || {}
7
10
  for (const [key, valueArray] of Object.entries(menuVisibleWhen)) {
8
- const menuValue = _menuState[key]
11
+ const menuValue = menuState[key]
9
12
  if (!valueArray.includes(menuValue)) {
10
13
  return false
11
14
  }
@@ -1,6 +1,10 @@
1
- import { isVisibleWhen, setMenuState } from './isVisibleWhen.js'
1
+ import { isVisibleWhen, attachMenuStateRef } from './isVisibleWhen.js'
2
2
  import { datasetRegistry } from './datasetRegistry.js'
3
3
 
4
+ const setMenuState = (menuState) => {
5
+ attachMenuStateRef({ current: { menuState } })
6
+ }
7
+
4
8
  describe('isVisibleWhen', () => {
5
9
  beforeEach(() => {
6
10
  // reset menu state before each test
@@ -64,4 +68,10 @@ describe('isVisibleWhen', () => {
64
68
  const visibleWhen = 'incorrectly-configured'
65
69
  expect(isVisibleWhen(visibleWhen)).toBe(true)
66
70
  })
71
+
72
+ it('returns false when menu condition is checked and menuState has not been set up', () => {
73
+ attachMenuStateRef({}) // current is undefined — exercises the || {} fallback
74
+ const visibleWhen = { menu: { datasets: ['floodZones'] } }
75
+ expect(isVisibleWhen(visibleWhen)).toBe(false)
76
+ })
67
77
  })
@@ -0,0 +1,125 @@
1
+ import { getBboxArray, bboxContains, expandBbox, bboxIntersects, getGeometryBbox } from './bbox.js'
2
+
3
+ // ─── getBboxArray ─────────────────────────────────────────────────────────────
4
+
5
+ describe('getBboxArray', () => {
6
+ it('returns [west, south, east, north] from a map bounds object', () => {
7
+ const map = {
8
+ getBounds: () => ({
9
+ getWest: () => -1,
10
+ getSouth: () => 50,
11
+ getEast: () => 1,
12
+ getNorth: () => 52
13
+ })
14
+ }
15
+ expect(getBboxArray(map)).toEqual([-1, 50, 1, 52])
16
+ })
17
+ })
18
+
19
+ // ─── bboxContains ─────────────────────────────────────────────────────────────
20
+
21
+ describe('bboxContains', () => {
22
+ const outer = [-2, 49, 2, 53]
23
+
24
+ it('returns false when outer is null', () => {
25
+ expect(bboxContains(null, [-1, 50, 1, 52])).toBe(false)
26
+ })
27
+
28
+ it('returns false when inner is null', () => {
29
+ expect(bboxContains(outer, null)).toBe(false)
30
+ })
31
+
32
+ it('returns true when inner is fully inside outer', () => {
33
+ expect(bboxContains(outer, [-1, 50, 1, 52])).toBe(true)
34
+ })
35
+
36
+ it('returns false when inner extends outside outer', () => {
37
+ expect(bboxContains(outer, [-3, 50, 1, 52])).toBe(false)
38
+ })
39
+ })
40
+
41
+ // ─── expandBbox ───────────────────────────────────────────────────────────────
42
+
43
+ describe('expandBbox', () => {
44
+ it('returns a copy of addition when existing is null', () => {
45
+ expect(expandBbox(null, [1, 2, 3, 4])).toEqual([1, 2, 3, 4])
46
+ })
47
+
48
+ it('returns the bounding union of existing and addition', () => {
49
+ expect(expandBbox([-1, 49, 1, 51], [-2, 50, 2, 52])).toEqual([-2, 49, 2, 52])
50
+ })
51
+ })
52
+
53
+ // ─── bboxIntersects ───────────────────────────────────────────────────────────
54
+
55
+ describe('bboxIntersects', () => {
56
+ it('returns false when a is null', () => {
57
+ expect(bboxIntersects(null, [0, 0, 1, 1])).toBe(false)
58
+ })
59
+
60
+ it('returns false when b is null', () => {
61
+ expect(bboxIntersects([0, 0, 1, 1], null)).toBe(false)
62
+ })
63
+
64
+ it('returns true when bboxes overlap', () => {
65
+ expect(bboxIntersects([0, 0, 2, 2], [1, 1, 3, 3])).toBe(true)
66
+ })
67
+
68
+ it('returns false when bboxes do not overlap', () => {
69
+ expect(bboxIntersects([0, 0, 1, 1], [2, 2, 3, 3])).toBe(false)
70
+ })
71
+ })
72
+
73
+ // ─── getGeometryBbox ──────────────────────────────────────────────────────────
74
+
75
+ describe('getGeometryBbox', () => {
76
+ it('handles a Point', () => {
77
+ expect(getGeometryBbox({ type: 'Point', coordinates: [10, 20] }))
78
+ .toEqual([10, 20, 10, 20])
79
+ })
80
+
81
+ it('handles a LineString', () => {
82
+ expect(getGeometryBbox({ type: 'LineString', coordinates: [[0, 0], [2, 4]] }))
83
+ .toEqual([0, 0, 2, 4])
84
+ })
85
+
86
+ it('handles a MultiPoint', () => {
87
+ expect(getGeometryBbox({ type: 'MultiPoint', coordinates: [[1, 2], [3, 4]] }))
88
+ .toEqual([1, 2, 3, 4])
89
+ })
90
+
91
+ it('handles a Polygon', () => {
92
+ const ring = [[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]]
93
+ expect(getGeometryBbox({ type: 'Polygon', coordinates: [ring] }))
94
+ .toEqual([0, 0, 5, 5])
95
+ })
96
+
97
+ it('handles a MultiLineString', () => {
98
+ expect(getGeometryBbox({
99
+ type: 'MultiLineString',
100
+ coordinates: [[[0, 0], [1, 1]], [[2, 2], [3, 3]]]
101
+ })).toEqual([0, 0, 3, 3])
102
+ })
103
+
104
+ it('handles a MultiPolygon', () => {
105
+ const poly1 = [[[0, 0], [0, 2], [2, 2], [2, 0], [0, 0]]]
106
+ const poly2 = [[[3, 3], [3, 5], [5, 5], [5, 3], [3, 3]]]
107
+ expect(getGeometryBbox({ type: 'MultiPolygon', coordinates: [poly1, poly2] }))
108
+ .toEqual([0, 0, 5, 5])
109
+ })
110
+
111
+ it('handles a GeometryCollection', () => {
112
+ expect(getGeometryBbox({
113
+ type: 'GeometryCollection',
114
+ geometries: [
115
+ { type: 'Point', coordinates: [1, 2] },
116
+ { type: 'Point', coordinates: [5, 6] }
117
+ ]
118
+ })).toEqual([1, 2, 5, 6])
119
+ })
120
+
121
+ it('throws for an unsupported geometry type', () => {
122
+ expect(() => getGeometryBbox({ type: 'Triangle', coordinates: [] }))
123
+ .toThrow('Unsupported geometry type: Triangle')
124
+ })
125
+ })