@defra/interactive-map 0.0.3-alpha → 0.0.5-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 (407) hide show
  1. package/README.md +16 -57
  2. package/dist/css/index.css +1 -1
  3. package/dist/esm/im-core.js +1 -1
  4. package/dist/esm/index.js +1 -1
  5. package/dist/umd/im-core.js +1 -1
  6. package/dist/umd/index.js +1 -1
  7. package/docs/api/button-definition.md +176 -0
  8. package/docs/api/context.md +124 -0
  9. package/docs/api/control-definition.md +72 -0
  10. package/docs/api/icon-definition.md +28 -0
  11. package/docs/api/map-style-config.md +76 -0
  12. package/docs/api/marker-config.md +51 -0
  13. package/docs/api/panel-definition.md +117 -0
  14. package/docs/api/slots.md +25 -0
  15. package/docs/api.md +721 -0
  16. package/docs/architecture.md +139 -0
  17. package/docs/building-a-plugin.md +149 -0
  18. package/docs/getting-started.md +46 -0
  19. package/docs/govuk-prototype.md +0 -0
  20. package/docs/plugins/interact.md +205 -0
  21. package/docs/plugins/map-styles.md +84 -0
  22. package/docs/plugins/plugin-context.md +67 -0
  23. package/docs/plugins/plugin-descriptor.md +71 -0
  24. package/docs/plugins/plugin-manifest.md +118 -0
  25. package/docs/plugins/scale-bar.md +62 -0
  26. package/docs/plugins/search.md +162 -0
  27. package/docs/plugins.md +116 -0
  28. package/govuk-prototype-kit.config.json +7 -7
  29. package/package.json +16 -2
  30. package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +2 -0
  31. package/plugins/beta/datasets/dist/esm/index.js +2 -0
  32. package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +2 -0
  33. package/plugins/beta/datasets/dist/umd/index.js +2 -0
  34. package/plugins/beta/datasets/src/datasets.js +113 -0
  35. package/plugins/beta/datasets/src/fetch/createDynamicSource.js +206 -0
  36. package/plugins/beta/datasets/src/fetch/fetchGeoJSON.js +38 -0
  37. package/plugins/{datasets → beta/datasets}/src/handleSetMapStyle.js +8 -1
  38. package/plugins/{datasets → beta/datasets}/src/manifest.js +1 -1
  39. package/plugins/{datasets → beta/datasets}/src/mapLayers.js +42 -4
  40. package/plugins/{datasets → beta/datasets}/src/panels/Key.jsx +1 -1
  41. package/plugins/beta/datasets/src/utils/bbox.js +115 -0
  42. package/plugins/beta/draw-ml/dist/css/index.css +1 -0
  43. package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -0
  44. package/plugins/beta/draw-ml/dist/esm/index.js +2 -0
  45. package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -0
  46. package/plugins/beta/draw-ml/dist/umd/index.js +2 -0
  47. package/plugins/beta/draw-ml/src/api/addFeature.js +27 -0
  48. package/plugins/{draw-ml → beta/draw-ml}/src/api/editFeature.js +17 -6
  49. package/plugins/beta/draw-ml/src/api/merge.js +9 -0
  50. package/plugins/beta/draw-ml/src/api/newLine.js +68 -0
  51. package/plugins/beta/draw-ml/src/api/newPolygon.js +68 -0
  52. package/plugins/beta/draw-ml/src/api/split.js +81 -0
  53. package/plugins/{draw-ml → beta/draw-ml}/src/defaults.js +3 -0
  54. package/plugins/beta/draw-ml/src/draw.scss +32 -0
  55. package/plugins/beta/draw-ml/src/events.js +171 -0
  56. package/plugins/beta/draw-ml/src/manifest.js +132 -0
  57. package/plugins/{draw-ml → beta/draw-ml}/src/mapboxDraw.js +51 -2
  58. package/plugins/{draw-ml → beta/draw-ml}/src/mapboxSnap.js +18 -12
  59. package/plugins/beta/draw-ml/src/modes/createDrawMode.js +497 -0
  60. package/plugins/beta/draw-ml/src/modes/drawLineMode.js +18 -0
  61. package/plugins/beta/draw-ml/src/modes/drawPolygonMode.js +16 -0
  62. package/plugins/beta/draw-ml/src/modes/editVertex/geometryHelpers.js +135 -0
  63. package/plugins/beta/draw-ml/src/modes/editVertex/helpers.js +2 -0
  64. package/plugins/beta/draw-ml/src/modes/editVertex/touchHandlers.js +134 -0
  65. package/plugins/beta/draw-ml/src/modes/editVertex/undoHandlers.js +133 -0
  66. package/plugins/beta/draw-ml/src/modes/editVertex/vertexOperations.js +141 -0
  67. package/plugins/beta/draw-ml/src/modes/editVertex/vertexQueries.js +121 -0
  68. package/plugins/beta/draw-ml/src/modes/editVertexMode.js +437 -0
  69. package/plugins/{draw-ml → beta/draw-ml}/src/reducer.js +30 -2
  70. package/plugins/{draw-ml → beta/draw-ml}/src/styles.js +66 -36
  71. package/plugins/beta/draw-ml/src/undoStack.js +50 -0
  72. package/plugins/beta/draw-ml/src/utils/debounce.js +16 -0
  73. package/plugins/beta/draw-ml/src/utils/flattenStyleProperties.js +49 -0
  74. package/plugins/beta/draw-ml/src/utils/spatial.js +258 -0
  75. package/plugins/beta/frame/dist/esm/im-frame-plugin.js +1 -0
  76. package/plugins/beta/frame/dist/esm/index.js +2 -0
  77. package/plugins/beta/frame/dist/umd/im-frame-plugin.js +1 -0
  78. package/plugins/beta/frame/dist/umd/index.js +2 -0
  79. package/plugins/{frame → beta/frame}/src/Frame.jsx +1 -1
  80. package/plugins/{map-styles → beta/map-styles}/dist/css/index.css +1 -1
  81. package/plugins/beta/map-styles/dist/esm/im-map-styles-plugin.js +1 -0
  82. package/plugins/beta/map-styles/dist/esm/index.js +2 -0
  83. package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -0
  84. package/plugins/beta/map-styles/dist/umd/index.js +2 -0
  85. package/plugins/{map-styles → beta/map-styles}/src/MapStyles.jsx +1 -1
  86. package/plugins/{map-styles → beta/map-styles}/src/manifest.js +8 -10
  87. package/plugins/{map-styles → beta/map-styles}/src/mapStyles.scss +4 -1
  88. package/plugins/beta/scale-bar/dist/esm/im-scale-bar-plugin.js +1 -0
  89. package/plugins/beta/scale-bar/dist/esm/index.js +2 -0
  90. package/plugins/beta/scale-bar/dist/umd/im-scale-bar-plugin.js +1 -0
  91. package/plugins/beta/scale-bar/dist/umd/index.js +2 -0
  92. package/plugins/beta/use-location/dist/esm/im-use-location-plugin.js +1 -0
  93. package/plugins/beta/use-location/dist/esm/index.js +2 -0
  94. package/plugins/beta/use-location/dist/umd/im-use-location-plugin.js +1 -0
  95. package/plugins/beta/use-location/dist/umd/index.js +2 -0
  96. package/plugins/interact/dist/css/index.css +1 -1
  97. package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
  98. package/plugins/interact/dist/esm/index.js +1 -1
  99. package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
  100. package/plugins/interact/dist/umd/index.js +1 -1
  101. package/plugins/interact/src/InteractInit.jsx +1 -1
  102. package/plugins/interact/src/InteractInit.test.js +97 -0
  103. package/plugins/interact/src/api/clear.test.js +18 -0
  104. package/plugins/interact/src/api/disable.test.js +12 -0
  105. package/plugins/interact/src/api/enable.test.js +48 -0
  106. package/plugins/interact/src/api/selectFeature.test.js +33 -0
  107. package/plugins/interact/src/api/unselectFeature.test.js +33 -0
  108. package/plugins/interact/src/defaults.js +2 -0
  109. package/plugins/interact/src/events.js +37 -52
  110. package/plugins/interact/src/events.test.js +186 -0
  111. package/plugins/interact/src/hooks/useHighlightSync.js +1 -1
  112. package/plugins/interact/src/hooks/useHighlightSync.test.js +212 -0
  113. package/plugins/interact/src/hooks/useInteractionHandlers.js +80 -41
  114. package/plugins/interact/src/hooks/useInteractionHandlers.test.js +316 -0
  115. package/plugins/interact/src/index.test.js +22 -0
  116. package/plugins/interact/src/interact.scss +6 -0
  117. package/plugins/interact/src/manifest.js +1 -1
  118. package/plugins/interact/src/manifest.test.js +94 -0
  119. package/plugins/interact/src/reducer.js +28 -21
  120. package/plugins/interact/src/reducer.test.js +155 -0
  121. package/plugins/interact/src/utils/buildStylesMap.test.js +57 -0
  122. package/plugins/interact/src/utils/featureQueries.js +2 -2
  123. package/plugins/interact/src/utils/featureQueries.test.js +56 -0
  124. package/plugins/interact/src/utils/spatial.js +95 -0
  125. package/plugins/interact/src/utils/spatial.test.js +93 -0
  126. package/plugins/search/dist/css/index.css +1 -1
  127. package/plugins/search/dist/esm/im-search-plugin.js +1 -1
  128. package/plugins/search/dist/esm/index.js +1 -1
  129. package/plugins/search/dist/umd/im-search-plugin.js +1 -1
  130. package/plugins/search/dist/umd/index.js +1 -1
  131. package/plugins/search/src/Search.jsx +2 -2
  132. package/plugins/search/src/components/Form/Form.jsx +1 -1
  133. package/plugins/search/src/components/Suggestions/Suggestions.jsx +5 -5
  134. package/plugins/search/src/datasets.js +1 -1
  135. package/plugins/search/src/defaults.js +3 -0
  136. package/plugins/search/src/events/fetchSuggestions.js +48 -53
  137. package/plugins/search/src/events/formHandlers.js +3 -2
  138. package/plugins/search/src/events/index.js +1 -1
  139. package/plugins/search/src/events/inputHandlers.js +3 -1
  140. package/plugins/search/src/events/suggestionHandlers.js +12 -3
  141. package/plugins/search/src/search.scss +4 -1
  142. package/plugins/search/src/utils/parseOsNamesResults.js +15 -19
  143. package/providers/{esri → beta/esri}/src/esriProvider.js +1 -1
  144. package/providers/{esri → beta/esri}/src/index.js +1 -1
  145. package/providers/{esri → beta/esri}/src/mapEvents.js +2 -2
  146. package/providers/beta/open-names/dist/esm/im-reverse-geocode.js +2 -0
  147. package/providers/beta/open-names/dist/esm/index.js +2 -0
  148. package/providers/beta/open-names/dist/umd/im-reverse-geocode.js +2 -0
  149. package/providers/beta/open-names/dist/umd/im-reverse-geocode.js.LICENSE.txt +1 -0
  150. package/providers/beta/open-names/dist/umd/index.js +2 -0
  151. package/providers/beta/open-names/dist/umd/index.js.LICENSE.txt +1 -0
  152. package/providers/maplibre/dist/esm/im-maplibre-framework.js +1 -1
  153. package/providers/maplibre/dist/esm/im-maplibre-framework.js.LICENSE.txt +1 -1
  154. package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
  155. package/providers/maplibre/dist/esm/index.js +1 -1
  156. package/providers/maplibre/dist/umd/im-maplibre-framework.js +1 -1
  157. package/providers/maplibre/dist/umd/im-maplibre-framework.js.LICENSE.txt +1 -1
  158. package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
  159. package/providers/maplibre/dist/umd/index.js +1 -1
  160. package/providers/maplibre/src/defaults.js +3 -2
  161. package/providers/maplibre/src/index.js +36 -20
  162. package/providers/maplibre/src/maplibreProvider.js +140 -11
  163. package/providers/maplibre/src/utils/detectWebgl.js +1 -2
  164. package/providers/maplibre/src/utils/highlightFeatures.js +78 -67
  165. package/providers/maplibre/src/utils/labels.js +174 -120
  166. package/providers/maplibre/src/utils/maplibreFixes.js +2 -2
  167. package/providers/maplibre/src/utils/queryFeatures.js +146 -0
  168. package/providers/maplibre/src/utils/spatial.js +14 -17
  169. package/sonar-project.properties +40 -3
  170. package/src/App/components/Actions/Actions.jsx +2 -2
  171. package/src/App/components/Actions/Actions.module.scss +2 -2
  172. package/src/App/components/KeyboardHelp/KeyboardHelp.jsx +3 -1
  173. package/src/App/components/KeyboardHelp/KeyboardHelp.test.jsx +9 -0
  174. package/src/App/components/Logo/Logo.jsx +1 -1
  175. package/src/App/components/MapButton/MapButton.jsx +217 -41
  176. package/src/App/components/MapButton/MapButton.module.scss +32 -21
  177. package/src/App/components/MapButton/MapButton.test.jsx +128 -84
  178. package/src/App/components/Markers/Markers.jsx +1 -1
  179. package/src/App/components/Markers/Markers.module.scss +0 -5
  180. package/src/App/components/Panel/Panel.jsx +75 -45
  181. package/src/App/components/Panel/Panel.module.scss +18 -24
  182. package/src/App/components/Panel/Panel.test.jsx +18 -0
  183. package/src/App/components/PopupMenu/PopupMenu.jsx +246 -0
  184. package/src/App/components/PopupMenu/PopupMenu.module.scss +70 -0
  185. package/src/App/components/PopupMenu/PopupMenu.test.jsx +304 -0
  186. package/src/App/components/Tooltip/Tooltip.jsx +4 -2
  187. package/src/App/components/Tooltip/getTooltipPosition.js +1 -1
  188. package/src/App/components/Viewport/MapController.jsx +1 -1
  189. package/src/App/components/Viewport/MapStatus.jsx +1 -1
  190. package/src/App/components/Viewport/Viewport.jsx +2 -2
  191. package/src/App/components/Viewport/Viewport.module.scss +2 -2
  192. package/src/App/controls/keyboardActions.js +3 -2
  193. package/src/App/controls/keyboardShortcuts.js +4 -2
  194. package/src/App/hooks/useButtonStateEvaluator.js +58 -45
  195. package/src/App/hooks/useButtonStateEvaluator.test.js +36 -3
  196. package/src/App/hooks/useCrossHairAPI.js +49 -47
  197. package/src/App/hooks/useFocusVisible.js +2 -2
  198. package/src/App/hooks/useInterfaceAPI.js +36 -1
  199. package/src/App/hooks/useKeyboardHint.js +1 -1
  200. package/src/App/hooks/useKeyboardShortcuts.js +4 -2
  201. package/src/App/hooks/useMapAnnouncements.js +34 -32
  202. package/src/App/hooks/useMapEvents.js +12 -1
  203. package/src/App/hooks/useMapProviderOverrides.js +3 -3
  204. package/src/App/hooks/useMapStateSync.js +7 -1
  205. package/src/App/hooks/useMapURLSync.js +6 -1
  206. package/src/App/hooks/useMarkersAPI.js +88 -50
  207. package/src/App/hooks/useMediaQueryDispatch.test.js +48 -0
  208. package/src/App/hooks/useModalPanelBehaviour.js +46 -40
  209. package/src/App/hooks/useResizeObserver.js +2 -2
  210. package/src/App/initialiseApp.js +45 -38
  211. package/src/App/layout/Layout.jsx +6 -4
  212. package/src/App/layout/layout.module.scss +13 -17
  213. package/src/App/registry/keyboardShortcutRegistry.js +12 -2
  214. package/src/App/registry/keyboardShortcutRegistry.test.js +28 -0
  215. package/src/App/registry/mergeManifests.js +1 -1
  216. package/src/App/registry/panelRegistry.js +1 -1
  217. package/src/App/registry/panelRegistry.test.js +30 -1
  218. package/src/App/registry/pluginRegistry.js +14 -51
  219. package/src/App/registry/pluginRegistry.test.js +54 -6
  220. package/src/App/renderer/HtmlElementHost.jsx +186 -0
  221. package/src/App/renderer/HtmlElementHost.test.jsx +231 -0
  222. package/src/App/renderer/mapButtons.js +12 -9
  223. package/src/App/renderer/mapButtons.test.js +6 -4
  224. package/src/App/renderer/mapControls.js +6 -0
  225. package/src/App/renderer/mapControls.test.js +10 -2
  226. package/src/App/renderer/mapPanels.js +39 -26
  227. package/src/App/renderer/mapPanels.test.js +29 -0
  228. package/src/App/renderer/pluginWrapper.test.js +28 -0
  229. package/src/App/renderer/slotHelpers.js +60 -0
  230. package/src/App/renderer/slotHelpers.test.js +82 -0
  231. package/src/App/store/AppProvider.jsx +3 -0
  232. package/src/App/store/AppProvider.test.jsx +40 -0
  233. package/src/App/store/PluginProvider.jsx +7 -5
  234. package/src/App/store/appActionsMap.js +46 -1
  235. package/src/App/store/appActionsMap.test.js +21 -0
  236. package/src/App/store/appReducer.js +1 -0
  237. package/src/App/store/mapActionsMap.js +12 -24
  238. package/src/App/store/mapReducer.js +1 -1
  239. package/src/InteractiveMap/InteractiveMap.js +194 -13
  240. package/src/InteractiveMap/InteractiveMap.test.js +164 -2
  241. package/src/InteractiveMap/behaviourController.js +12 -2
  242. package/src/InteractiveMap/behaviourController.test.js +82 -1
  243. package/src/InteractiveMap/deviceChecker.js +1 -1
  244. package/src/InteractiveMap/deviceChecker.test.js +2 -2
  245. package/src/InteractiveMap/historyManager.js +41 -5
  246. package/src/InteractiveMap/historyManager.test.js +37 -6
  247. package/src/InteractiveMap/polyfills.js +39 -0
  248. package/src/InteractiveMap/polyfills.test.js +127 -0
  249. package/src/config/appConfig.js +5 -5
  250. package/src/config/appConfig.test.js +107 -42
  251. package/src/config/defaults.js +30 -20
  252. package/src/config/events.js +110 -4
  253. package/src/index.js +5 -2
  254. package/src/index.umd.js +1 -1
  255. package/src/scss/main.scss +1 -0
  256. package/src/scss/settings/_dimensions.scss +6 -2
  257. package/src/scss/tools/_border-focus.scss +20 -4
  258. package/src/services/eventBus.test.js +24 -1
  259. package/src/test-utils.js +1 -0
  260. package/src/types.js +590 -0
  261. package/src/utils/detectBreakpoint.js +78 -77
  262. package/src/utils/detectBreakpoint.test.js +50 -4
  263. package/src/utils/detectInterfaceType.js +4 -4
  264. package/src/utils/getIsFullscreen.js +3 -1
  265. package/src/utils/getSafeZoneInset.js +23 -7
  266. package/src/utils/getSafeZoneInset.test.js +18 -0
  267. package/src/utils/mapStateSync.js +3 -3
  268. package/src/utils/queryString.js +1 -1
  269. package/src/utils/stringToKebab.js +1 -1
  270. package/src/utils/toggleInertElements.js +37 -12
  271. package/webpack.dev.mjs +3 -4
  272. package/webpack.esm.mjs +8 -8
  273. package/webpack.umd.mjs +8 -8
  274. package/plugins/datasets/dist/esm/im-datasets-plugin.js +0 -1
  275. package/plugins/datasets/dist/esm/index.js +0 -2
  276. package/plugins/datasets/dist/umd/im-datasets-plugin.js +0 -1
  277. package/plugins/datasets/dist/umd/index.js +0 -2
  278. package/plugins/datasets/src/datasets.js +0 -60
  279. package/plugins/datasets/src/utils/bbox.js +0 -4
  280. package/plugins/draw-ml/dist/css/index.css +0 -1
  281. package/plugins/draw-ml/dist/esm/im-draw-ml-plugin.js +0 -1
  282. package/plugins/draw-ml/dist/esm/index.js +0 -2
  283. package/plugins/draw-ml/dist/umd/im-draw-ml-plugin.js +0 -1
  284. package/plugins/draw-ml/dist/umd/index.js +0 -2
  285. package/plugins/draw-ml/src/api/addFeature.js +0 -15
  286. package/plugins/draw-ml/src/api/newPolygon.js +0 -43
  287. package/plugins/draw-ml/src/draw.scss +0 -9
  288. package/plugins/draw-ml/src/events.js +0 -127
  289. package/plugins/draw-ml/src/manifest.js +0 -100
  290. package/plugins/draw-ml/src/modes/drawVertexMode.js +0 -275
  291. package/plugins/draw-ml/src/modes/editVertexMode.js +0 -426
  292. package/plugins/draw-ml/src/utils.js +0 -102
  293. package/plugins/frame/dist/esm/im-frame-plugin.js +0 -1
  294. package/plugins/frame/dist/esm/index.js +0 -2
  295. package/plugins/frame/dist/umd/im-frame-plugin.js +0 -1
  296. package/plugins/frame/dist/umd/index.js +0 -2
  297. package/plugins/map-styles/dist/esm/im-map-styles-plugin.js +0 -1
  298. package/plugins/map-styles/dist/esm/index.js +0 -2
  299. package/plugins/map-styles/dist/umd/im-map-styles-plugin.js +0 -1
  300. package/plugins/map-styles/dist/umd/index.js +0 -2
  301. package/plugins/scale-bar/dist/esm/im-scale-bar-plugin.js +0 -1
  302. package/plugins/scale-bar/dist/esm/index.js +0 -2
  303. package/plugins/scale-bar/dist/umd/im-scale-bar-plugin.js +0 -1
  304. package/plugins/scale-bar/dist/umd/index.js +0 -2
  305. package/plugins/search/src/utils/fetchDataset.js +0 -23
  306. package/plugins/use-location/dist/esm/im-use-location-plugin.js +0 -1
  307. package/plugins/use-location/dist/esm/index.js +0 -2
  308. package/plugins/use-location/dist/umd/im-use-location-plugin.js +0 -1
  309. package/plugins/use-location/dist/umd/index.js +0 -2
  310. package/providers/maplibre/dist/esm/im-maplibre-legacy-framework.js +0 -1
  311. package/providers/maplibre/dist/umd/im-maplibre-legacy-framework.js +0 -1
  312. package/providers/open-names/dist/esm/im-reverse-geocode.js +0 -2
  313. package/providers/open-names/dist/esm/index.js +0 -2
  314. package/providers/open-names/dist/umd/im-reverse-geocode.js +0 -2
  315. package/providers/open-names/dist/umd/index.js +0 -2
  316. /package/docs/architecture/{ARCHITECTURE_DIAGRAMS.md → architecture-diagrams.md} +0 -0
  317. /package/plugins/{datasets → beta/datasets}/dist/css/index.css +0 -0
  318. /package/plugins/{datasets/dist/esm/index.js.LICENSE.txt → beta/datasets/dist/esm/im-datasets-plugin.js.LICENSE.txt} +0 -0
  319. /package/plugins/{datasets/dist/umd → beta/datasets/dist/esm}/index.js.LICENSE.txt +0 -0
  320. /package/plugins/{draw-ml/dist/esm/index.js.LICENSE.txt → beta/datasets/dist/umd/im-datasets-plugin.js.LICENSE.txt} +0 -0
  321. /package/plugins/{draw-ml → beta/datasets}/dist/umd/index.js.LICENSE.txt +0 -0
  322. /package/plugins/{datasets → beta/datasets}/src/DatasetsInit.jsx +0 -0
  323. /package/plugins/{datasets → beta/datasets}/src/api/addDataset.js +0 -0
  324. /package/plugins/{datasets → beta/datasets}/src/api/hideDataset.js +0 -0
  325. /package/plugins/{datasets → beta/datasets}/src/api/hideFeatures.js +0 -0
  326. /package/plugins/{datasets → beta/datasets}/src/api/removeDataset.js +0 -0
  327. /package/plugins/{datasets → beta/datasets}/src/api/showDataset.js +0 -0
  328. /package/plugins/{datasets → beta/datasets}/src/api/showFeatures.js +0 -0
  329. /package/plugins/{datasets → beta/datasets}/src/datasets.scss +0 -0
  330. /package/plugins/{datasets → beta/datasets}/src/defaults.js +0 -0
  331. /package/plugins/{datasets → beta/datasets}/src/index.js +0 -0
  332. /package/plugins/{datasets → beta/datasets}/src/panels/Key.module.scss +0 -0
  333. /package/plugins/{datasets → beta/datasets}/src/panels/Layers.jsx +0 -0
  334. /package/plugins/{datasets → beta/datasets}/src/panels/Layers.module.scss +0 -0
  335. /package/plugins/{datasets → beta/datasets}/src/reducer.js +0 -0
  336. /package/plugins/{datasets → beta/datasets}/src/utils/debounce.js +0 -0
  337. /package/plugins/{datasets → beta/datasets}/src/utils/filters.js +0 -0
  338. /package/plugins/{draw-es → beta/draw-es}/src/DrawInit.jsx +0 -0
  339. /package/plugins/{draw-es → beta/draw-es}/src/api/addFeature.js +0 -0
  340. /package/plugins/{draw-es → beta/draw-es}/src/api/deleteFeature.js +0 -0
  341. /package/plugins/{draw-es → beta/draw-es}/src/api/editFeature.js +0 -0
  342. /package/plugins/{draw-es → beta/draw-es}/src/api/newPolygon.js +0 -0
  343. /package/plugins/{draw-es → beta/draw-es}/src/events.js +0 -0
  344. /package/plugins/{draw-es → beta/draw-es}/src/graphic.js +0 -0
  345. /package/plugins/{draw-es → beta/draw-es}/src/index.js +0 -0
  346. /package/plugins/{draw-es → beta/draw-es}/src/manifest.js +0 -0
  347. /package/plugins/{draw-es → beta/draw-es}/src/reducer.js +0 -0
  348. /package/plugins/{draw-es → beta/draw-es}/src/sketchViewModel.js +0 -0
  349. /package/plugins/{frame → beta/draw-ml}/dist/esm/index.js.LICENSE.txt +0 -0
  350. /package/plugins/{frame → beta/draw-ml}/dist/umd/index.js.LICENSE.txt +0 -0
  351. /package/plugins/{draw-ml → beta/draw-ml}/src/DrawInit.jsx +0 -0
  352. /package/plugins/{draw-ml → beta/draw-ml}/src/api/deleteFeature.js +0 -0
  353. /package/plugins/{draw-ml → beta/draw-ml}/src/index.js +0 -0
  354. /package/plugins/{draw-ml → beta/draw-ml}/src/modes/disabledMode.js +0 -0
  355. /package/plugins/{draw-ml/src → beta/draw-ml/src/utils}/snapHelpers.js +0 -0
  356. /package/plugins/{frame → beta/frame}/dist/css/index.css +0 -0
  357. /package/plugins/{map-styles → beta/frame}/dist/esm/index.js.LICENSE.txt +0 -0
  358. /package/plugins/{map-styles → beta/frame}/dist/umd/index.js.LICENSE.txt +0 -0
  359. /package/plugins/{frame → beta/frame}/package.json +0 -0
  360. /package/plugins/{frame → beta/frame}/src/FrameInit.jsx +0 -0
  361. /package/plugins/{frame → beta/frame}/src/api/addFrame.js +0 -0
  362. /package/plugins/{frame → beta/frame}/src/api/editFeature.js +0 -0
  363. /package/plugins/{frame → beta/frame}/src/config.js +0 -0
  364. /package/plugins/{frame → beta/frame}/src/frame.scss +0 -0
  365. /package/plugins/{frame → beta/frame}/src/index.js +0 -0
  366. /package/plugins/{frame → beta/frame}/src/manifest.js +0 -0
  367. /package/plugins/{frame → beta/frame}/src/reducer.js +0 -0
  368. /package/plugins/{frame → beta/frame}/src/utils.js +0 -0
  369. /package/plugins/{scale-bar → beta/map-styles}/dist/esm/index.js.LICENSE.txt +0 -0
  370. /package/plugins/{scale-bar → beta/map-styles}/dist/umd/index.js.LICENSE.txt +0 -0
  371. /package/plugins/{map-styles → beta/map-styles}/src/MapStylesInit.jsx +0 -0
  372. /package/plugins/{map-styles → beta/map-styles}/src/config.js +0 -0
  373. /package/plugins/{map-styles → beta/map-styles}/src/index.js +0 -0
  374. /package/plugins/{scale-bar → beta/scale-bar}/dist/css/index.css +0 -0
  375. /package/plugins/{use-location → beta/scale-bar}/dist/esm/index.js.LICENSE.txt +0 -0
  376. /package/plugins/{use-location → beta/scale-bar}/dist/umd/index.js.LICENSE.txt +0 -0
  377. /package/plugins/{scale-bar → beta/scale-bar}/package.json +0 -0
  378. /package/plugins/{scale-bar → beta/scale-bar}/src/ScaleBar.jsx +0 -0
  379. /package/plugins/{scale-bar → beta/scale-bar}/src/ScaleBar.test.jsx +0 -0
  380. /package/plugins/{scale-bar → beta/scale-bar}/src/index.js +0 -0
  381. /package/plugins/{scale-bar → beta/scale-bar}/src/index.test.js +0 -0
  382. /package/plugins/{scale-bar → beta/scale-bar}/src/manifest.js +0 -0
  383. /package/plugins/{scale-bar → beta/scale-bar}/src/scaleBar.scss +0 -0
  384. /package/plugins/{scale-bar → beta/scale-bar}/src/utils.js +0 -0
  385. /package/plugins/{scale-bar → beta/scale-bar}/src/utils.test.js +0 -0
  386. /package/{providers/open-names → plugins/beta/use-location}/dist/esm/index.js.LICENSE.txt +0 -0
  387. /package/{providers/open-names → plugins/beta/use-location}/dist/umd/index.js.LICENSE.txt +0 -0
  388. /package/plugins/{use-location → beta/use-location}/src/UseLocation.jsx +0 -0
  389. /package/plugins/{use-location → beta/use-location}/src/UseLocationInit.jsx +0 -0
  390. /package/plugins/{use-location → beta/use-location}/src/defaults.js +0 -0
  391. /package/plugins/{use-location → beta/use-location}/src/events.js +0 -0
  392. /package/plugins/{use-location → beta/use-location}/src/index.js +0 -0
  393. /package/plugins/{use-location → beta/use-location}/src/manifest.js +0 -0
  394. /package/plugins/{use-location → beta/use-location}/src/reducer.js +0 -0
  395. /package/providers/{esri → beta/esri}/src/appEvents.js +0 -0
  396. /package/providers/{esri → beta/esri}/src/defaults.js +0 -0
  397. /package/providers/{esri → beta/esri}/src/esriProvider.scss +0 -0
  398. /package/providers/{esri → beta/esri}/src/utils/coords.js +0 -0
  399. /package/providers/{esri → beta/esri}/src/utils/detectWebGL.js +0 -0
  400. /package/providers/{esri → beta/esri}/src/utils/esriFixes.js +0 -0
  401. /package/providers/{esri → beta/esri}/src/utils/query.js +0 -0
  402. /package/providers/{esri → beta/esri}/src/utils/spatial.js +0 -0
  403. /package/providers/{open-names → beta/open-names}/dist/esm/im-reverse-geocode.js.LICENSE.txt +0 -0
  404. /package/providers/{open-names/dist/umd/im-reverse-geocode.js.LICENSE.txt → beta/open-names/dist/esm/index.js.LICENSE.txt} +0 -0
  405. /package/providers/{open-names → beta/open-names}/src/index.js +0 -0
  406. /package/providers/{open-names → beta/open-names}/src/reverseGeocode.js +0 -0
  407. /package/providers/{open-names → beta/open-names}/src/utils/mapToLocationModel.js +0 -0
@@ -4,8 +4,10 @@
4
4
 
5
5
  import { setupBehavior, shouldLoadComponent } from './behaviourController.js'
6
6
  import * as queryString from '../utils/queryString.js'
7
+ import { updateDOMState } from './domStateManager.js'
7
8
 
8
9
  jest.mock('../utils/queryString.js')
10
+ jest.mock('./domStateManager.js', () => ({ updateDOMState: jest.fn() }))
9
11
 
10
12
  describe('shouldLoadComponent', () => {
11
13
  beforeEach(() => {
@@ -60,8 +62,12 @@ describe('setupBehavior', () => {
60
62
  mockMapInstance = {
61
63
  config: { hybridWidth: null, maxMobileWidth: 640 },
62
64
  _breakpointDetector: mockBreakpointDetector,
65
+ _root: null,
66
+ _isHidden: false,
63
67
  loadApp: jest.fn(),
64
- removeApp: jest.fn()
68
+ removeApp: jest.fn(),
69
+ hideApp: jest.fn(),
70
+ showApp: jest.fn()
65
71
  }
66
72
  // Default: viewport is wide
67
73
  window.matchMedia = jest.fn().mockImplementation(() => ({
@@ -145,4 +151,79 @@ describe('setupBehavior', () => {
145
151
  const cleanup = setupBehavior(mockMapInstance)
146
152
  expect(cleanup).toBeNull()
147
153
  })
154
+
155
+ describe('hybrid behaviour handleChange', () => {
156
+ let handleChange
157
+
158
+ beforeEach(() => {
159
+ const mockAddEventListener = jest.fn((event, cb) => { handleChange = cb })
160
+ window.matchMedia = jest.fn().mockImplementation(() => ({
161
+ matches: false,
162
+ addEventListener: mockAddEventListener,
163
+ removeEventListener: jest.fn()
164
+ }))
165
+ mockMapInstance.config = { id: 'test', behaviour: 'hybrid', hybridWidth: null, maxMobileWidth: 640 }
166
+ setupBehavior(mockMapInstance)
167
+ })
168
+
169
+ it('calls showApp when map is hidden and should load', () => {
170
+ mockMapInstance._isHidden = true
171
+ queryString.getQueryParam.mockReturnValue(null) // wide viewport, should load
172
+
173
+ handleChange()
174
+
175
+ expect(mockMapInstance.showApp).toHaveBeenCalled()
176
+ expect(mockMapInstance.loadApp).not.toHaveBeenCalled()
177
+ })
178
+
179
+ it('calls loadApp when map has no root and should load', () => {
180
+ mockMapInstance._isHidden = false
181
+ mockMapInstance._root = null
182
+ queryString.getQueryParam.mockReturnValue(null)
183
+
184
+ handleChange()
185
+
186
+ expect(mockMapInstance.loadApp).toHaveBeenCalled()
187
+ expect(mockMapInstance.showApp).not.toHaveBeenCalled()
188
+ })
189
+
190
+ it('calls updateDOMState when map is showing and should load', () => {
191
+ mockMapInstance._isHidden = false
192
+ mockMapInstance._root = {} // has root
193
+ queryString.getQueryParam.mockReturnValue(null)
194
+
195
+ handleChange()
196
+
197
+ expect(updateDOMState).toHaveBeenCalledWith(mockMapInstance)
198
+ expect(mockMapInstance.loadApp).not.toHaveBeenCalled()
199
+ expect(mockMapInstance.showApp).not.toHaveBeenCalled()
200
+ })
201
+
202
+ it('calls hideApp when should not load and has root', () => {
203
+ mockMapInstance._root = {}
204
+ // Simulate narrow viewport where shouldLoadComponent returns false
205
+ window.matchMedia = jest.fn().mockImplementation(() => ({ matches: true }))
206
+ queryString.getQueryParam.mockReturnValue(null)
207
+
208
+ handleChange()
209
+
210
+ expect(mockMapInstance.hideApp).toHaveBeenCalled()
211
+ })
212
+
213
+ it('does nothing when should not load and map has no root', () => {
214
+ mockMapInstance._root = null
215
+ mockMapInstance._isHidden = false
216
+
217
+ // Force shouldLoadComponent === false
218
+ window.matchMedia = jest.fn().mockImplementation(() => ({ matches: true }))
219
+ queryString.getQueryParam.mockReturnValue(null)
220
+
221
+ handleChange()
222
+
223
+ expect(mockMapInstance.hideApp).not.toHaveBeenCalled()
224
+ expect(mockMapInstance.loadApp).not.toHaveBeenCalled()
225
+ expect(mockMapInstance.showApp).not.toHaveBeenCalled()
226
+ expect(updateDOMState).not.toHaveBeenCalled()
227
+ })
228
+ })
148
229
  })
@@ -19,7 +19,7 @@ export function checkDeviceSupport (rootEl, config) {
19
19
 
20
20
  if (!mapProvider) {
21
21
  console.log('No map provider')
22
- return
22
+ return false
23
23
  }
24
24
 
25
25
  if (!device?.isSupported) {
@@ -49,13 +49,13 @@ describe('checkDeviceSupport', () => {
49
49
  expect(console.log).toHaveBeenCalledWith('WebGL not available')
50
50
  })
51
51
 
52
- it('logs "No map provider" and returns undefined if mapProvider is missing', () => {
52
+ it('logs "No map provider" and returns false if mapProvider is missing', () => {
53
53
  config = {
54
54
  mapProvider: null,
55
55
  deviceNotSupportedText: 'Device not supported'
56
56
  }
57
57
 
58
- expect(checkDeviceSupport(rootEl, config)).toBeUndefined()
58
+ expect(checkDeviceSupport(rootEl, config)).toBe(false)
59
59
  expect(console.log).toHaveBeenCalledWith('No map provider')
60
60
  expect(renderError).not.toHaveBeenCalled()
61
61
  expect(removeLoadingState).not.toHaveBeenCalled()
@@ -6,6 +6,40 @@ import defaults from '../config/defaults.js'
6
6
  // Internal helpers
7
7
  // -----------------------------------------------------------------------------
8
8
 
9
+ /**
10
+ * Opens the map application for a given map instance.
11
+ *
12
+ * If the map instance was previously hidden, it restores the existing app.
13
+ * Otherwise, it loads the app for the first time.
14
+ *
15
+ * @param {MapInstance} mapInstance
16
+ * The map instance whose application should be opened.
17
+ */
18
+ function openMap (mapInstance) {
19
+ if (mapInstance._isHidden) {
20
+ mapInstance.showApp?.()
21
+ } else {
22
+ mapInstance.loadApp?.()
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Closes the map application for a given map instance.
28
+ *
29
+ * Depending on the configuration, the map state is either preserved
30
+ * by hiding the app or fully removed from the DOM.
31
+ *
32
+ * @param {MapInstance} mapInstance
33
+ * The map instance whose application should be closed.
34
+ */
35
+ function closeMap (mapInstance) {
36
+ if (mapInstance.config.preserveStateOnClose) {
37
+ mapInstance.hideApp?.()
38
+ } else {
39
+ mapInstance.removeApp?.()
40
+ }
41
+ }
42
+
9
43
  /**
10
44
  * Handles the `popstate` event triggered by browser back/forward navigation.
11
45
  *
@@ -28,11 +62,13 @@ function handlePopstate () {
28
62
  const isHybridVisible = mapInstance.config.behaviour === 'hybrid' && !isHybridFullscreen(mapInstance.config)
29
63
  const isOpen = mapInstance.rootEl?.children.length
30
64
 
31
- if (shouldBeOpen && !isOpen) {
32
- mapInstance.loadApp?.()
33
- } else if (!shouldBeOpen && isOpen && !isHybridVisible) {
34
- mapInstance.removeApp?.()
35
- mapInstance.openButton?.focus?.()
65
+ if (shouldBeOpen && (!isOpen || mapInstance._isHidden)) {
66
+ openMap(mapInstance)
67
+ continue
68
+ }
69
+
70
+ if (!shouldBeOpen && isOpen && !isHybridVisible) {
71
+ closeMap(mapInstance)
36
72
  }
37
73
  }
38
74
  }
@@ -13,19 +13,25 @@ describe('historyManager', () => {
13
13
  beforeEach(() => {
14
14
  component1 = {
15
15
  id: 'map',
16
- config: { behaviour: 'buttonFirst', hybridWidth: null, maxMobileWidth: 640 },
16
+ config: { behaviour: 'buttonFirst', hybridWidth: null, maxMobileWidth: 640, preserveStateOnClose: false },
17
17
  rootEl: document.createElement('div'),
18
18
  loadApp: jest.fn(),
19
19
  removeApp: jest.fn(),
20
- openButton: { focus: jest.fn() }
20
+ hideApp: jest.fn(),
21
+ showApp: jest.fn(),
22
+ openButton: { focus: jest.fn() },
23
+ _isHidden: false
21
24
  }
22
25
  component2 = {
23
26
  id: 'list',
24
- config: { behaviour: 'hybrid', hybridWidth: null, maxMobileWidth: 640 },
27
+ config: { behaviour: 'hybrid', hybridWidth: null, maxMobileWidth: 640, preserveStateOnClose: false },
25
28
  rootEl: document.createElement('div'),
26
29
  loadApp: jest.fn(),
27
30
  removeApp: jest.fn(),
28
- openButton: { focus: jest.fn() }
31
+ hideApp: jest.fn(),
32
+ showApp: jest.fn(),
33
+ openButton: { focus: jest.fn() },
34
+ _isHidden: false
29
35
  }
30
36
  popstateEvent = new PopStateEvent('popstate')
31
37
  jest.clearAllMocks()
@@ -63,7 +69,8 @@ describe('historyManager', () => {
63
69
  expect(component1.loadApp).not.toHaveBeenCalled()
64
70
  })
65
71
 
66
- it('removes component and focuses button when view param does not match', () => {
72
+ it('removes component when view param does not match and preserveStateOnClose is false', () => {
73
+ component1.config.preserveStateOnClose = false
67
74
  component1.rootEl.appendChild(document.createElement('div'))
68
75
  historyManager.register(component1)
69
76
  queryString.getQueryParam.mockReturnValue(null)
@@ -71,7 +78,19 @@ describe('historyManager', () => {
71
78
  window.dispatchEvent(popstateEvent)
72
79
 
73
80
  expect(component1.removeApp).toHaveBeenCalled()
74
- expect(component1.openButton.focus).toHaveBeenCalled()
81
+ expect(component1.hideApp).not.toHaveBeenCalled()
82
+ })
83
+
84
+ it('hides component when view param does not match and preserveStateOnClose is true', () => {
85
+ component1.config.preserveStateOnClose = true
86
+ component1.rootEl.appendChild(document.createElement('div'))
87
+ historyManager.register(component1)
88
+ queryString.getQueryParam.mockReturnValue(null)
89
+
90
+ window.dispatchEvent(popstateEvent)
91
+
92
+ expect(component1.hideApp).toHaveBeenCalled()
93
+ expect(component1.removeApp).not.toHaveBeenCalled()
75
94
  })
76
95
 
77
96
  it('does not remove hybrid component when viewport is wide (inline mode)', () => {
@@ -87,6 +106,7 @@ describe('historyManager', () => {
87
106
  })
88
107
 
89
108
  it('removes hybrid component when viewport is narrow and view does not match', () => {
109
+ component2.config.preserveStateOnClose = false
90
110
  component2.rootEl.appendChild(document.createElement('div'))
91
111
  historyManager.register(component2)
92
112
  queryString.getQueryParam.mockReturnValue(null)
@@ -98,6 +118,17 @@ describe('historyManager', () => {
98
118
  expect(component2.removeApp).toHaveBeenCalled()
99
119
  })
100
120
 
121
+ it('calls showApp when view param matches and component is hidden', () => {
122
+ component1._isHidden = true
123
+ historyManager.register(component1)
124
+ queryString.getQueryParam.mockReturnValue('map')
125
+
126
+ window.dispatchEvent(popstateEvent)
127
+
128
+ expect(component1.showApp).toHaveBeenCalled()
129
+ expect(component1.loadApp).not.toHaveBeenCalled()
130
+ })
131
+
101
132
  it('uses hybridWidth for media query when provided', () => {
102
133
  component2.config.hybridWidth = 768
103
134
  component2.rootEl.appendChild(document.createElement('div'))
@@ -0,0 +1,39 @@
1
+ // crypto.randomUUID
2
+ if (typeof crypto !== 'undefined' && !crypto.randomUUID) {
3
+ let last = 0
4
+ crypto.randomUUID = () => {
5
+ last = Math.max(Date.now(), last + 1)
6
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c, i) => {
7
+ const v = i < 12 ? Number.parseInt(last.toString(16).padStart(12, '0')[i], 16) : Math.random() * 16 | 0 // NOSONAR
8
+ return (c === 'x' ? v : (v & 0x3 | 0x8)).toString(16)
9
+ })
10
+ }
11
+ }
12
+
13
+ // AbortSignal.throwIfAborted
14
+ const needsThrowIfAborted = typeof AbortController !== 'undefined' &&
15
+ !Object.getPrototypeOf(new AbortController().signal).throwIfAborted
16
+
17
+ if (needsThrowIfAborted) {
18
+ const signalProto = Object.getPrototypeOf(new AbortController().signal)
19
+ signalProto.throwIfAborted = function () {
20
+ if (this.aborted) {
21
+ const err = new Error('The operation was aborted.')
22
+ err.name = 'AbortError'
23
+ throw err
24
+ }
25
+ }
26
+ }
27
+
28
+ // Inject polyfill into web workers created from blob URLs (e.g. MapLibre GL)
29
+ // Workers have their own global scope so main thread polyfills don't apply
30
+ if (needsThrowIfAborted && typeof URL !== 'undefined' && URL.createObjectURL) {
31
+ const _createObjectURL = URL.createObjectURL.bind(URL)
32
+ URL.createObjectURL = (blob) => {
33
+ if (blob instanceof Blob && blob.type === 'text/javascript') {
34
+ const p = 'if(typeof AbortController!=="undefined"){var _p=Object.getPrototypeOf(new AbortController().signal);if(!_p.throwIfAborted){_p.throwIfAborted=function(){if(this.aborted){var e=new Error("The operation was aborted.");e.name="AbortError";throw e}}}}\n'
35
+ blob = new Blob([p, blob], { type: 'text/javascript' })
36
+ }
37
+ return _createObjectURL(blob)
38
+ }
39
+ }
@@ -0,0 +1,127 @@
1
+ describe('Polyfills', () => {
2
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/
3
+
4
+ const originalCryptoUUID = crypto.randomUUID
5
+ const originalCreateObjectURL = URL.createObjectURL
6
+ const signalProto = Object.getPrototypeOf(new AbortController().signal)
7
+ const originalThrowIfAborted = signalProto.throwIfAborted
8
+
9
+ beforeEach(() => {
10
+ jest.resetModules()
11
+ })
12
+
13
+ afterEach(() => {
14
+ Object.defineProperty(crypto, 'randomUUID', {
15
+ value: originalCryptoUUID,
16
+ configurable: true,
17
+ writable: true
18
+ })
19
+
20
+ URL.createObjectURL = originalCreateObjectURL
21
+
22
+ if (originalThrowIfAborted) {
23
+ signalProto.throwIfAborted = originalThrowIfAborted
24
+ } else {
25
+ delete signalProto.throwIfAborted
26
+ }
27
+ })
28
+
29
+ const load = () => jest.isolateModules(() => require('./polyfills.js'))
30
+
31
+ // Helper to read Blob text in environments without blob.text()
32
+ const readBlobText = (blob) => new Promise((resolve) => {
33
+ const reader = new FileReader()
34
+ reader.onload = () => resolve(reader.result)
35
+ reader.readAsText(blob)
36
+ })
37
+
38
+ describe('crypto.randomUUID', () => {
39
+ test('polyfills crypto.randomUUID when missing (Lines 3-8)', () => {
40
+ Object.defineProperty(crypto, 'randomUUID', {
41
+ value: undefined,
42
+ configurable: true,
43
+ writable: true
44
+ })
45
+
46
+ load()
47
+
48
+ expect(typeof crypto.randomUUID).toBe('function')
49
+ expect(crypto.randomUUID()).toMatch(UUID_RE)
50
+ })
51
+
52
+ test('crypto.randomUUID generates unique UUIDs', () => {
53
+ Object.defineProperty(crypto, 'randomUUID', {
54
+ value: undefined,
55
+ configurable: true,
56
+ writable: true
57
+ })
58
+ load()
59
+ const ids = new Set(Array.from({ length: 100 }, () => crypto.randomUUID()))
60
+ expect(ids.size).toBe(100)
61
+ })
62
+
63
+ test('does not overwrite existing crypto.randomUUID', () => {
64
+ const fake = jest.fn(() => 'fake')
65
+ Object.defineProperty(crypto, 'randomUUID', {
66
+ value: fake,
67
+ configurable: true,
68
+ writable: true
69
+ })
70
+ load()
71
+ expect(crypto.randomUUID).toBe(fake)
72
+ })
73
+ })
74
+
75
+ describe('AbortSignal.throwIfAborted', () => {
76
+ test('throws AbortError when aborted (True branch)', () => {
77
+ delete signalProto.throwIfAborted
78
+ load()
79
+ const ac = new AbortController()
80
+ ac.abort()
81
+ expect(() => ac.signal.throwIfAborted()).toThrow('The operation was aborted.')
82
+ })
83
+
84
+ test('does nothing when not aborted (False branch)', () => {
85
+ delete signalProto.throwIfAborted
86
+ load()
87
+ const ac = new AbortController()
88
+ // This call should execute line 20, see that aborted is false, and return undefined
89
+ expect(() => ac.signal.throwIfAborted()).not.toThrow()
90
+ })
91
+
92
+ test('wraps URL.createObjectURL for JS blobs', async () => {
93
+ delete signalProto.throwIfAborted
94
+
95
+ const mockCreate = jest.fn(() => 'blob:mock')
96
+ URL.createObjectURL = mockCreate
97
+
98
+ load()
99
+
100
+ const content = 'console.log(1)'
101
+ const blob = new Blob([content], { type: 'text/javascript' })
102
+ const result = URL.createObjectURL(blob)
103
+
104
+ expect(result).toBe('blob:mock')
105
+ expect(mockCreate).toHaveBeenCalled()
106
+
107
+ const passedBlob = mockCreate.mock.calls[0][0]
108
+ const text = await readBlobText(passedBlob)
109
+
110
+ expect(text).toContain('throwIfAborted')
111
+ expect(text).toContain(content)
112
+ })
113
+
114
+ test('does not wrap URL.createObjectURL for non-JS blobs', () => {
115
+ delete signalProto.throwIfAborted
116
+ const mockCreate = jest.fn(() => 'blob:mock')
117
+ URL.createObjectURL = mockCreate
118
+
119
+ load()
120
+
121
+ const blob = new Blob(['{}'], { type: 'application/json' })
122
+ URL.createObjectURL(blob)
123
+
124
+ expect(mockCreate).toHaveBeenCalledWith(blob)
125
+ })
126
+ })
127
+ })
@@ -18,12 +18,12 @@ const exitButtonSlots = {
18
18
  }
19
19
 
20
20
  // Default app buttons, panels and icons
21
- export const appConfig = {
21
+ export const defaultAppConfig = {
22
22
  buttons: [{
23
23
  id: 'exit',
24
24
  label: 'Exit',
25
25
  iconId: 'close',
26
- onClick: (e, { services }) => services.closeApp(),
26
+ onClick: (_e, { services }) => services.closeApp(),
27
27
  excludeWhen: ({ appConfig, appState }) => !appConfig.hasExitButton || !(appState.isFullscreen && (new URL(window.location.href)).searchParams.has(appConfig.mapViewParamKey)),
28
28
  mobile: exitButtonSlots,
29
29
  tablet: exitButtonSlots,
@@ -32,7 +32,7 @@ export const appConfig = {
32
32
  id: 'fullscreen',
33
33
  label: () => `${document.fullscreenElement ? 'Exit' : 'Enter'} fullscreen`,
34
34
  iconId: () => document.fullscreenElement ? 'minimise' : 'maximise',
35
- onClick: (e, { appState }) => {
35
+ onClick: (_e, { appState }) => {
36
36
  const container = appState.layoutRefs.appContainerRef.current
37
37
  document.fullscreenElement ? document.exitFullscreen() : container.requestFullscreen()
38
38
  },
@@ -45,7 +45,7 @@ export const appConfig = {
45
45
  group: 'zoom',
46
46
  label: 'Zoom in',
47
47
  iconId: 'plus',
48
- onClick: (e, { mapProvider, appConfig }) => mapProvider.zoomIn(appConfig.zoomDelta),
48
+ onClick: (_e, { mapProvider, appConfig }) => mapProvider.zoomIn(appConfig.zoomDelta),
49
49
  excludeWhen: ({ appState, appConfig }) => !appConfig.enableZoomControls || appState.interfaceType === 'touch',
50
50
  enableWhen: ({ mapState }) => !mapState.isAtMaxZoom,
51
51
  mobile: buttonSlots,
@@ -56,7 +56,7 @@ export const appConfig = {
56
56
  group: 'zoom',
57
57
  label: 'Zoom out',
58
58
  iconId: 'minus',
59
- onClick: (e, { mapProvider, appConfig }) => mapProvider.zoomOut(appConfig.zoomDelta),
59
+ onClick: (_e, { mapProvider, appConfig }) => mapProvider.zoomOut(appConfig.zoomDelta),
60
60
  excludeWhen: ({ appState, appConfig }) => !appConfig.enableZoomControls || appState.interfaceType === 'touch',
61
61
  enableWhen: ({ mapState }) => !mapState.isAtMinZoom,
62
62
  mobile: buttonSlots,
@@ -1,75 +1,140 @@
1
1
  import { render } from '@testing-library/react'
2
- import { appConfig } from './appConfig'
2
+ import { defaultAppConfig, defaultButtonConfig, scaleFactor, markerSvgPaths } from './appConfig'
3
3
 
4
- describe('appConfig', () => {
5
- const appState = { layoutRefs: { appContainerRef: { current: document.createElement('div') } }, isFullscreen: false }
6
- const buttons = appConfig.buttons
4
+ describe('defaultAppConfig', () => {
5
+ const appState = {
6
+ layoutRefs: { appContainerRef: { current: document.createElement('div') } },
7
+ isFullscreen: false,
8
+ interfaceType: 'mouse'
9
+ }
10
+
11
+ const buttons = defaultAppConfig.buttons
7
12
  const fullscreenBtn = buttons.find(b => b.id === 'fullscreen')
8
13
  const exitBtn = buttons.find(b => b.id === 'exit')
14
+ const zoomInBtn = buttons.find(b => b.id === 'zoomIn')
15
+ const zoomOutBtn = buttons.find(b => b.id === 'zoomOut')
9
16
 
17
+ // --- UI RENDER TESTS ---
10
18
  it('renders KeyboardHelp panel', () => {
11
- const panel = appConfig.panels.find(p => p.id === 'keyboardHelp')
19
+ const panel = defaultAppConfig.panels.find(p => p.id === 'keyboardHelp')
12
20
  const { container } = render(panel.render())
13
21
  expect(container.querySelector('.im-c-keyboard-help')).toBeInTheDocument()
14
22
  })
15
23
 
16
- it('evaluates dynamic button properties', () => {
17
- // label
18
- expect(typeof fullscreenBtn.label).toBe('function')
19
- expect(fullscreenBtn.label({ appState, appConfig })).toMatch(/fullscreen/)
24
+ // --- EXIT BUTTON (Line 27 Coverage) ---
25
+ it('covers all branches of exitBtn excludeWhen', () => {
26
+ const config = { hasExitButton: true, mapViewParamKey: 'view' }
27
+
28
+ expect(exitBtn.excludeWhen({
29
+ appConfig: { ...config, hasExitButton: false },
30
+ appState: { isFullscreen: true }
31
+ })).toBe(true)
32
+
33
+ window.history.pushState({}, '', '?view=map')
34
+ expect(exitBtn.excludeWhen({
35
+ appConfig: config,
36
+ appState: { isFullscreen: false }
37
+ })).toBe(true)
38
+
39
+ window.history.pushState({}, '', '?wrong=param')
40
+ expect(exitBtn.excludeWhen({
41
+ appConfig: config,
42
+ appState: { isFullscreen: true }
43
+ })).toBe(true)
44
+
45
+ window.history.pushState({}, '', '?view=map')
46
+ expect(exitBtn.excludeWhen({
47
+ appConfig: config,
48
+ appState: { isFullscreen: true }
49
+ })).toBe(false)
50
+ })
51
+
52
+ it('calls exit button onClick correctly', () => {
53
+ const servicesMock = { closeApp: jest.fn() }
54
+ exitBtn.onClick({}, { services: servicesMock })
55
+ expect(servicesMock.closeApp).toHaveBeenCalled()
56
+ })
57
+
58
+ // --- FULLSCREEN BUTTON (Line 39 Coverage) ---
59
+ it('evaluates fullscreen label and icon states', () => {
60
+ const containerMock = { requestFullscreen: jest.fn() }
61
+ Object.defineProperty(document, 'fullscreenElement', { value: null, writable: true, configurable: true })
20
62
 
21
- // iconId
22
- expect(typeof fullscreenBtn.iconId).toBe('function')
23
- expect(fullscreenBtn.iconId({ appState, appConfig })).toBe('maximise')
63
+ expect(fullscreenBtn.label({ appState })).toBe('Enter fullscreen')
64
+ expect(fullscreenBtn.iconId({ appState })).toBe('maximise')
24
65
 
25
- // excludeWhen
26
- expect(exitBtn.excludeWhen({ appConfig: { hasExitButton: false } })).toBe(true)
27
- expect(fullscreenBtn.excludeWhen({ appState, appConfig: { enableFullscreen: true } })).toBe(false)
66
+ Object.defineProperty(document, 'fullscreenElement', { value: containerMock, writable: true, configurable: true })
67
+ expect(fullscreenBtn.label({ appState })).toBe('Exit fullscreen')
68
+ expect(fullscreenBtn.iconId({ appState })).toBe('minimise')
69
+ })
70
+
71
+ it('covers all branches of fullscreen excludeWhen', () => {
72
+ expect(fullscreenBtn.excludeWhen({ appConfig: { enableFullscreen: false }, appState: { isFullscreen: false } })).toBe(true)
73
+ expect(fullscreenBtn.excludeWhen({ appConfig: { enableFullscreen: true }, appState: { isFullscreen: true } })).toBe(true)
74
+ expect(fullscreenBtn.excludeWhen({ appConfig: { enableFullscreen: true }, appState: { isFullscreen: false } })).toBe(false)
28
75
  })
29
76
 
30
77
  it('calls fullscreen onClick correctly', () => {
31
78
  const containerMock = { requestFullscreen: jest.fn() }
32
- const appStateMock = { layoutRefs: { appContainerRef: { current: containerMock } }, isFullscreen: false }
33
-
34
- Object.defineProperty(document, 'fullscreenElement', { value: null, writable: true })
79
+ const appStateMock = { layoutRefs: { appContainerRef: { current: containerMock } } }
35
80
  document.exitFullscreen = jest.fn()
36
81
 
37
- // Enter fullscreen
82
+ Object.defineProperty(document, 'fullscreenElement', { value: null, writable: true, configurable: true })
38
83
  fullscreenBtn.onClick({}, { appState: appStateMock })
39
84
  expect(containerMock.requestFullscreen).toHaveBeenCalled()
40
- expect(document.exitFullscreen).not.toHaveBeenCalled()
41
85
 
42
- // Exit fullscreen
43
- Object.defineProperty(document, 'fullscreenElement', { value: containerMock })
86
+ Object.defineProperty(document, 'fullscreenElement', { value: containerMock, writable: true, configurable: true })
44
87
  fullscreenBtn.onClick({}, { appState: appStateMock })
45
88
  expect(document.exitFullscreen).toHaveBeenCalled()
46
89
  })
47
90
 
48
- it('evaluates fullscreen button label and iconId for both fullscreen states', () => {
49
- const containerMock = { requestFullscreen: jest.fn() }
50
-
51
- // Not in fullscreen
52
- Object.defineProperty(document, 'fullscreenElement', { value: null, writable: true })
53
- expect(fullscreenBtn.label({ appState, appConfig })).toBe('Enter fullscreen')
54
- expect(fullscreenBtn.iconId({ appState, appConfig })).toBe('maximise')
91
+ // --- ZOOM BUTTONS (Line 60 & 70 Coverage) ---
92
+ it('covers all branches of zoom excludeWhen for BOTH buttons', () => {
93
+ // We run this test for both ZoomIn and ZoomOut to ensure
94
+ // identical lines in both button configs are covered.
95
+ [zoomInBtn, zoomOutBtn].forEach((btn) => {
96
+ // Branch A: First part of OR is true (!enableZoomControls)
97
+ expect(btn.excludeWhen({
98
+ appConfig: { enableZoomControls: false },
99
+ appState: { interfaceType: 'mouse' }
100
+ })).toBe(true)
101
+
102
+ // Branch B: Second part of OR is true (interfaceType === 'touch')
103
+ expect(btn.excludeWhen({
104
+ appConfig: { enableZoomControls: true },
105
+ appState: { interfaceType: 'touch' }
106
+ })).toBe(true)
107
+
108
+ // Branch C: Both parts are false (Result: false)
109
+ expect(btn.excludeWhen({
110
+ appConfig: { enableZoomControls: true },
111
+ appState: { interfaceType: 'mouse' }
112
+ })).toBe(false)
113
+ })
114
+ })
55
115
 
56
- // In fullscreen
57
- Object.defineProperty(document, 'fullscreenElement', { value: containerMock, writable: true })
58
- expect(fullscreenBtn.label({ appState, appConfig })).toBe('Exit fullscreen')
59
- expect(fullscreenBtn.iconId({ appState, appConfig })).toBe('minimise')
116
+ it('evaluates zoom enableWhen logic', () => {
117
+ expect(zoomInBtn.enableWhen({ mapState: { isAtMaxZoom: true } })).toBe(false)
118
+ expect(zoomInBtn.enableWhen({ mapState: { isAtMaxZoom: false } })).toBe(true)
119
+ expect(zoomOutBtn.enableWhen({ mapState: { isAtMinZoom: true } })).toBe(false)
120
+ expect(zoomOutBtn.enableWhen({ mapState: { isAtMinZoom: false } })).toBe(true)
60
121
  })
61
122
 
62
- it('calls exit button onClick correctly', () => {
63
- const fakeEvent = {}
64
- const servicesMock = { closeApp: jest.fn() }
65
- const handleExitClickMock = jest.fn()
123
+ it('triggers mapProvider zoom methods on click', () => {
124
+ const mapProviderMock = { zoomIn: jest.fn(), zoomOut: jest.fn() }
125
+ const appConfigMock = { zoomDelta: 2 }
66
126
 
67
- exitBtn.onClick(fakeEvent, { services: servicesMock, appConfig: { handleExitClick: handleExitClickMock } })
127
+ zoomInBtn.onClick({}, { mapProvider: mapProviderMock, appConfig: appConfigMock })
128
+ expect(mapProviderMock.zoomIn).toHaveBeenCalledWith(2)
68
129
 
69
- // Assert that the services.closeApp() is called
70
- expect(servicesMock.closeApp).toHaveBeenCalled()
130
+ zoomOutBtn.onClick({}, { mapProvider: mapProviderMock, appConfig: appConfigMock })
131
+ expect(mapProviderMock.zoomOut).toHaveBeenCalledWith(2)
132
+ })
71
133
 
72
- // If your button no longer calls handleExitClick in the current logic, this should NOT be called
73
- expect(handleExitClickMock).not.toHaveBeenCalled()
134
+ // --- SUPPLEMENTARY CONFIGS ---
135
+ it('exports supplementary configs and constants', () => {
136
+ expect(defaultButtonConfig.label).toBe('Button')
137
+ expect(scaleFactor.large).toBe(2)
138
+ expect(markerSvgPaths[0].shape).toBe('pin')
74
139
  })
75
140
  })