@defra/interactive-map 0.0.30-alpha → 0.0.32-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 (247) hide show
  1. package/README.md +2 -2
  2. package/assets/images/bright-ofm-thumb.jpg +0 -0
  3. package/assets/images/dark-ofm-thumb.jpg +0 -0
  4. package/assets/images/road-ofm-thumb.jpg +0 -0
  5. package/assets/templates/add-polygons.njk +143 -0
  6. package/assets/templates/add-symbols.njk +119 -0
  7. package/assets/templates/{map.njk → basic-map.njk} +9 -17
  8. package/assets/templates/button-map.njk +56 -0
  9. package/assets/templates/draw-tools.njk +161 -0
  10. package/assets/templates/marker-panel.njk +91 -0
  11. package/assets/templates/place-marker.njk +104 -0
  12. package/assets/templates/search-control.njk +98 -0
  13. package/assets/templates/select-feature.njk +101 -0
  14. package/assets/templates/style-switcher.njk +87 -0
  15. package/assets/templates/toggle-marker-label.njk +74 -0
  16. package/dist/css/index.css +1 -1
  17. package/dist/esm/im-core.js +1 -1
  18. package/dist/esm/im-shell.js +1 -1
  19. package/dist/umd/im-core.js +1 -1
  20. package/dist/umd/index.js +1 -1
  21. package/docs/api/context.md +4 -4
  22. package/docs/api.md +166 -37
  23. package/docs/assets/css/docusaurus.scss +36 -4
  24. package/docs/assets/images/add-polygons.jpg +0 -0
  25. package/docs/assets/images/add-symbols.jpg +0 -0
  26. package/docs/assets/images/basic-map.jpg +0 -0
  27. package/docs/assets/images/button-first.jpg +0 -0
  28. package/docs/assets/images/deuteranopia-ozs-map-thumb.jpg +0 -0
  29. package/docs/assets/images/draw.jpg +0 -0
  30. package/docs/assets/images/marker-panel.jpg +0 -0
  31. package/docs/assets/images/night-ozs-map-thumb.jpg +0 -0
  32. package/docs/assets/images/outdoor-ozs-map-thumb.jpg +0 -0
  33. package/docs/assets/images/place-marker.jpg +0 -0
  34. package/docs/assets/images/screenshot.jpg +0 -0
  35. package/docs/assets/images/search.jpg +0 -0
  36. package/docs/assets/images/select-feature.jpg +0 -0
  37. package/docs/assets/images/style-switcher.jpg +0 -0
  38. package/docs/assets/images/toggle-marker-label.jpg +0 -0
  39. package/docs/assets/images/tritanopia-ozs-map-thumb.jpg +0 -0
  40. package/docs/examples/add-marker-with-panel.mdx +3 -3
  41. package/docs/examples/add-polygons.mdx +225 -0
  42. package/docs/examples/add-symbols.mdx +177 -0
  43. package/docs/examples/basic-map.mdx +2 -2
  44. package/docs/examples/draw-tools.mdx +271 -0
  45. package/docs/examples/index.mdx +95 -11
  46. package/docs/examples/place-marker.mdx +151 -0
  47. package/docs/examples/search-control.mdx +71 -0
  48. package/docs/examples/select-feature.mdx +146 -0
  49. package/docs/examples/style-switcher.mdx +135 -0
  50. package/docs/examples/toggle-marker-label.mdx +3 -3
  51. package/docs/plugins/datasets.md +157 -122
  52. package/docs/plugins/interact.md +1 -4
  53. package/docusaurus.config.cjs +9 -2
  54. package/govuk-prototype-kit.config.json +52 -2
  55. package/package.json +1 -1
  56. package/plugins/beta/datasets/dist/css/index.css +5 -2
  57. package/plugins/beta/datasets/dist/esm/im-datasets-ml-adapter.js +1 -0
  58. package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +1 -1
  59. package/plugins/beta/datasets/dist/esm/index.js +1 -1
  60. package/plugins/beta/datasets/dist/umd/im-datasets-ml-adapter.js +2 -0
  61. package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +2 -0
  62. package/plugins/beta/datasets/dist/umd/index.js +2 -0
  63. package/plugins/beta/datasets/src/adapters/loadLayerAdapter.js +33 -0
  64. package/plugins/beta/datasets/src/adapters/maplibre/layerBuilders.js +44 -120
  65. package/plugins/beta/datasets/src/adapters/maplibre/maplibreLayerAdapter.js +145 -245
  66. package/plugins/beta/datasets/src/adapters/maplibre/maplibreLayerAdapter.test.js +516 -0
  67. package/plugins/beta/datasets/src/adapters/maplibre/registry/mapLibreDataset.js +209 -0
  68. package/plugins/beta/datasets/src/adapters/maplibre/registry/mapLibreDataset.test.js +483 -0
  69. package/plugins/beta/datasets/src/api/addDataset.js +2 -5
  70. package/plugins/beta/datasets/src/api/addDataset.test.js +16 -0
  71. package/plugins/beta/datasets/src/api/getOpacity.js +14 -14
  72. package/plugins/beta/datasets/src/api/getOpacity.test.js +72 -0
  73. package/plugins/beta/datasets/src/api/getStyle.js +8 -9
  74. package/plugins/beta/datasets/src/api/getStyle.test.js +42 -0
  75. package/plugins/beta/datasets/src/api/removeDataset.js +7 -7
  76. package/plugins/beta/datasets/src/api/removeDataset.test.js +45 -0
  77. package/plugins/beta/datasets/src/api/setData.js +9 -3
  78. package/plugins/beta/datasets/src/api/setData.test.js +62 -0
  79. package/plugins/beta/datasets/src/api/setDatasetVisibility.js +5 -30
  80. package/plugins/beta/datasets/src/api/setDatasetVisibility.test.js +31 -0
  81. package/plugins/beta/datasets/src/api/setFeatureVisibility.js +9 -18
  82. package/plugins/beta/datasets/src/api/setFeatureVisibility.test.js +74 -0
  83. package/plugins/beta/datasets/src/api/setGlobals.js +20 -0
  84. package/plugins/beta/datasets/src/api/setGlobals.test.js +63 -0
  85. package/plugins/beta/datasets/src/api/setOpacity.js +8 -27
  86. package/plugins/beta/datasets/src/api/setOpacity.test.js +31 -0
  87. package/plugins/beta/datasets/src/api/setStyle.js +5 -21
  88. package/plugins/beta/datasets/src/api/setStyle.test.js +53 -0
  89. package/plugins/beta/datasets/src/components/Key/Key.jsx +51 -0
  90. package/plugins/beta/datasets/src/components/Key/KeyGroupItem.jsx +18 -0
  91. package/plugins/beta/datasets/src/components/Key/KeyItem.jsx +20 -0
  92. package/plugins/beta/datasets/src/components/Key/KeySvg.jsx +22 -0
  93. package/plugins/beta/datasets/src/components/Key/KeySvg.test.jsx +87 -0
  94. package/plugins/beta/datasets/src/components/Key/KeySvgLine.jsx +19 -0
  95. package/plugins/beta/datasets/src/components/Key/KeySvgLine.test.jsx +71 -0
  96. package/plugins/beta/datasets/src/components/{KeySvgPattern.jsx → Key/KeySvgPattern.jsx} +3 -4
  97. package/plugins/beta/datasets/src/components/Key/KeySvgPattern.test.jsx +55 -0
  98. package/plugins/beta/datasets/src/components/Key/KeySvgRect.jsx +22 -0
  99. package/plugins/beta/datasets/src/components/Key/KeySvgRect.test.jsx +85 -0
  100. package/plugins/beta/datasets/src/components/{KeySvgSymbol.jsx → Key/KeySvgSymbol.jsx} +5 -5
  101. package/plugins/beta/datasets/src/components/Key/KeySvgSymbol.test.jsx +80 -0
  102. package/plugins/beta/datasets/src/components/Key/svgProperties.test.js +61 -0
  103. package/plugins/beta/datasets/src/{panels → components/LayersMenu}/Layers.module.scss +15 -11
  104. package/plugins/beta/datasets/src/components/LayersMenu/LayersMenu.jsx +33 -0
  105. package/plugins/beta/datasets/src/components/LayersMenu/LayersMenuCheckbox.jsx +31 -0
  106. package/plugins/beta/datasets/src/components/LayersMenu/LayersMenuGroupWrapper.jsx +19 -0
  107. package/plugins/beta/datasets/src/datasets.scss +2 -2
  108. package/plugins/beta/datasets/src/fetch/createDynamicSource.js +8 -8
  109. package/plugins/beta/datasets/src/{DatasetsInit.jsx → initialise/DatasetsInit.jsx} +19 -21
  110. package/plugins/beta/datasets/src/{defaults.js → initialise/defaults.js} +17 -2
  111. package/plugins/beta/datasets/src/initialise/defaults.test.js +127 -0
  112. package/plugins/beta/datasets/src/{datasets.js → initialise/initialiseDatasets.js} +30 -26
  113. package/plugins/beta/datasets/src/initialise/initialiseDatasets.test.js +273 -0
  114. package/plugins/beta/datasets/src/manifest.js +8 -6
  115. package/plugins/beta/datasets/src/reducers/__data__/demoDatasets.js +253 -0
  116. package/plugins/beta/datasets/src/reducers/datasetsToMenu.js +83 -0
  117. package/plugins/beta/datasets/src/reducers/datasetsToMenu.test.js +84 -0
  118. package/plugins/beta/datasets/src/reducers/mappedDatasetsReducer.js +41 -0
  119. package/plugins/beta/datasets/src/reducers/mappedDatasetsReducer.test.js +117 -0
  120. package/plugins/beta/datasets/src/reducers/pluginState.js +206 -0
  121. package/plugins/beta/datasets/src/reducers/pluginState.test.js +386 -0
  122. package/plugins/beta/datasets/src/registry/__mocks__/datasetRegistry.js +29 -0
  123. package/plugins/beta/datasets/src/registry/createDataset.js +5 -0
  124. package/plugins/beta/datasets/src/registry/dataset.js +176 -0
  125. package/plugins/beta/datasets/src/registry/dataset.test.js +497 -0
  126. package/plugins/beta/datasets/src/registry/datasetDefinitionCache.js +35 -0
  127. package/plugins/beta/datasets/src/registry/datasetRegistry.js +116 -0
  128. package/plugins/beta/datasets/src/registry/datasetRegistry.test.js +335 -0
  129. package/plugins/beta/datasets/src/registry/dynamicGeoJson.js +17 -0
  130. package/plugins/beta/datasets/src/registry/globalDataset.js +36 -0
  131. package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
  132. package/plugins/beta/draw-es/src/api/addFeature.js +12 -3
  133. package/plugins/beta/draw-es/src/events.js +8 -3
  134. package/plugins/beta/draw-es/src/events.test.js +2 -0
  135. package/plugins/beta/draw-es/src/graphic.js +26 -2
  136. package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
  137. package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
  138. package/plugins/beta/draw-ml/src/DrawInit.jsx +8 -0
  139. package/plugins/beta/draw-ml/src/api/newLine.js +1 -0
  140. package/plugins/beta/draw-ml/src/api/newPolygon.js +1 -0
  141. package/plugins/beta/draw-ml/src/events.js +13 -1
  142. package/plugins/beta/draw-ml/src/index.js +1 -1
  143. package/plugins/beta/draw-ml/src/manifest.js +3 -0
  144. package/plugins/beta/draw-ml/src/modes/createDrawMode.js +21 -12
  145. package/plugins/beta/draw-ml/src/modes/editVertexMode.js +4 -0
  146. package/plugins/beta/draw-ol/dist/esm/EditMode.js +1 -1
  147. package/plugins/beta/draw-ol/dist/esm/im-draw-ol-plugin.js +1 -1
  148. package/plugins/beta/draw-ol/src/edit/keyboardHandler.js +4 -2
  149. package/plugins/beta/map-styles/src/mapStyles.scss +41 -41
  150. package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
  151. package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
  152. package/plugins/interact/dist/umd/index.js +1 -1
  153. package/plugins/interact/src/InteractInit.jsx +3 -5
  154. package/plugins/interact/src/InteractInit.test.js +1 -3
  155. package/plugins/interact/src/events.js +2 -29
  156. package/plugins/interact/src/events.test.js +4 -102
  157. package/plugins/interact/src/hooks/useAttachEvents.js +3 -4
  158. package/plugins/interact/src/hooks/useAttachEvents.test.js +1 -3
  159. package/plugins/interact/src/hooks/useCrossHairVisibility.js +4 -10
  160. package/plugins/interact/src/hooks/useCrossHairVisibility.test.js +5 -10
  161. package/plugins/interact/src/hooks/useHighlightSync.js +1 -10
  162. package/plugins/interact/src/hooks/useHighlightSync.test.js +2 -21
  163. package/plugins/interact/src/hooks/useInteractionHandlers.js +15 -12
  164. package/plugins/interact/src/hooks/useInteractionHandlers.test.js +28 -29
  165. package/plugins/interact/src/manifest.js +0 -35
  166. package/plugins/interact/src/manifest.test.js +3 -43
  167. package/plugins/interact/src/reducer.js +4 -22
  168. package/plugins/interact/src/reducer.test.js +11 -39
  169. package/plugins/search/dist/css/index.css +5 -2
  170. package/plugins/search/src/components/Form/Form.module.scss +5 -2
  171. package/plugins/search/src/search.scss +4 -4
  172. package/providers/beta/esri/dist/esm/im-esri-provider.js +1 -1
  173. package/providers/beta/esri/src/esriProvider.js +4 -0
  174. package/providers/beta/openlayers/dist/esm/im-openlayers-provider.js +1 -1
  175. package/providers/beta/openlayers/dist/umd/im-openlayers-provider.js +1 -1
  176. package/providers/beta/openlayers/src/openlayersProvider.js +4 -0
  177. package/providers/beta/openlayers/src/openlayersProvider.test.js +5 -0
  178. package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
  179. package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
  180. package/providers/maplibre/src/maplibreProvider.js +6 -4
  181. package/providers/maplibre/src/maplibreProvider.test.js +5 -0
  182. package/providers/maplibre/src/utils/highlightFeatures.js +74 -25
  183. package/providers/maplibre/src/utils/highlightFeatures.test.js +132 -0
  184. package/providers/maplibre/src/utils/patternImages.js +2 -4
  185. package/providers/maplibre/src/utils/symbolImages.js +1 -4
  186. package/rollup.esm.mjs +2 -8
  187. package/src/App/components/CrossHair/CrossHair.module.scss +8 -8
  188. package/src/App/components/MapButton/MapButton.jsx +16 -1
  189. package/src/App/components/MapButton/MapButton.module.scss +12 -1
  190. package/src/App/components/MapButton/MapButton.test.jsx +40 -1
  191. package/src/App/hooks/useContinueEnabledEvaluator.js +31 -0
  192. package/src/App/hooks/useContinueEnabledEvaluator.test.js +118 -0
  193. package/src/App/hooks/useEvaluateProp.js +15 -15
  194. package/src/App/hooks/useEvaluateProp.test.js +80 -76
  195. package/src/App/hooks/useLayoutMeasurements.js +11 -5
  196. package/src/App/hooks/useResizeObserver.js +15 -2
  197. package/src/App/hooks/useResizeObserver.test.js +1 -0
  198. package/src/App/renderer/PluginInits.jsx +2 -0
  199. package/src/App/renderer/mapButtons.js +21 -1
  200. package/src/App/renderer/mapButtons.test.js +46 -1
  201. package/src/App/store/appReducer.js +1 -1
  202. package/src/App/store/appReducer.test.js +37 -0
  203. package/src/InteractiveMap/InteractiveMap.js +14 -4
  204. package/src/InteractiveMap/InteractiveMap.test.js +11 -3
  205. package/src/InteractiveMap/behaviourController.js +3 -1
  206. package/src/InteractiveMap/behaviourController.test.js +6 -3
  207. package/src/InteractiveMap/domStateManager.js +2 -0
  208. package/src/config/appConfig.js +25 -0
  209. package/src/config/appConfig.test.js +50 -0
  210. package/src/config/defaults.js +1 -0
  211. package/src/config/events.js +21 -3
  212. package/src/scss/settings/_dimensions.scss +1 -0
  213. package/src/services/patternRegistry.js +8 -4
  214. package/src/types.js +7 -0
  215. package/src/utils/patternUtils.js +1 -2
  216. package/src/utils/patternUtils.test.js +9 -0
  217. package/webpack.dev.mjs +10 -0
  218. package/webpack.umd.mjs +1 -1
  219. package/docs/assets/images/screens-blue.jpg +0 -0
  220. package/docs/assets/images/screens-white.jpg +0 -0
  221. package/plugins/beta/datasets/dist/adapters/maplibre/esm/im-datasets-ml-adapter.js +0 -1
  222. package/plugins/beta/datasets/dist/adapters/maplibre/esm/index.js +0 -1
  223. package/plugins/beta/datasets/dist/umd/maplibre/im-datasets-maplibre-adapter.js +0 -2
  224. package/plugins/beta/datasets/dist/umd/maplibre/im-datasets-plugin.js +0 -2
  225. package/plugins/beta/datasets/dist/umd/maplibre/index.js +0 -2
  226. package/plugins/beta/datasets/src/adapters/maplibre/index.js +0 -18
  227. package/plugins/beta/datasets/src/adapters/maplibre/layerIds.js +0 -75
  228. package/plugins/beta/datasets/src/adapters/maplibre/patternImages.js +0 -27
  229. package/plugins/beta/datasets/src/adapters/maplibre/symbolImages.js +0 -31
  230. package/plugins/beta/datasets/src/components/KeySvg.jsx +0 -24
  231. package/plugins/beta/datasets/src/components/KeySvgLine.jsx +0 -19
  232. package/plugins/beta/datasets/src/components/KeySvgRect.jsx +0 -22
  233. package/plugins/beta/datasets/src/index.maplibre.umd.js +0 -23
  234. package/plugins/beta/datasets/src/panels/Key.jsx +0 -62
  235. package/plugins/beta/datasets/src/panels/Layers.jsx +0 -141
  236. package/plugins/beta/datasets/src/reducer.js +0 -219
  237. package/plugins/beta/datasets/src/reducers/keyReducer.js +0 -34
  238. package/plugins/beta/datasets/src/utils/filters.js +0 -34
  239. package/plugins/beta/datasets/src/utils/mergeSublayer.js +0 -86
  240. package/plugins/beta/draw-ml/dist/css/index.css +0 -21
  241. /package/plugins/beta/datasets/dist/umd/{maplibre/im-datasets-maplibre-adapter.js.LICENSE.txt → im-datasets-ml-adapter.js.LICENSE.txt} +0 -0
  242. /package/plugins/beta/datasets/dist/umd/{maplibre/im-datasets-plugin.js.LICENSE.txt → im-datasets-plugin.js.LICENSE.txt} +0 -0
  243. /package/plugins/beta/datasets/dist/umd/{maplibre/index.js.LICENSE.txt → index.js.LICENSE.txt} +0 -0
  244. /package/plugins/beta/datasets/src/components/{EmptyKey.jsx → Key/EmptyKey.jsx} +0 -0
  245. /package/plugins/beta/datasets/src/components/{EmptyKey.test.jsx → Key/EmptyKey.test.jsx} +0 -0
  246. /package/plugins/beta/datasets/src/{panels → components/Key}/Key.module.scss +0 -0
  247. /package/plugins/beta/datasets/src/components/{svgProperties.js → Key/svgProperties.js} +0 -0
@@ -0,0 +1,483 @@
1
+ import { MapLibreDataset } from './mapLibreDataset.js'
2
+ import { datasetRegistry } from '../../../registry/datasetRegistry.js'
3
+ import { logger } from '../../../../../../../src/services/logger.js'
4
+ // Use the mock datasetRegistry with the demo datasets attached before each test
5
+ // so we can test Dataset methods that depend on parent/sublayer relationships and styles
6
+ jest.mock('../../../registry/datasetRegistry.js')
7
+ jest.mock('../../../../../../../src/services/logger.js', () => ({
8
+ logger: { warn: jest.fn() }
9
+ }))
10
+
11
+ describe('MapLibreDataset', () => {
12
+ beforeEach(() => {
13
+ logger.warn.mockClear()
14
+ datasetRegistry.attachCreateDataset(def => new MapLibreDataset(def))
15
+ datasetRegistry.mockExtend({
16
+ // layerIds
17
+ 'ds-fill-only': { id: 'ds-fill-only', visible: true, minZoom: 10, maxZoom: 24, style: { fill: 'blue' } },
18
+ 'ds-pattern-only': { id: 'ds-pattern-only', style: { fillPattern: 'dots' } },
19
+ 'ds-transparent-fill': { id: 'ds-transparent-fill', style: { fill: 'transparent' } },
20
+ // shared: no special properties — used by layerIds, sourceId, source, visibility,
21
+ // _hiddenFeaturesIdExpression, _hiddenFeaturesFilter, and filter tests
22
+ 'ds-bare': { id: 'ds-bare' },
23
+ 'ds-no-id-prop': { id: 'ds-no-id-prop', geojson: 'https://example.com/data', transformRequest: () => {} },
24
+ 'ds-no-transform': { id: 'ds-no-transform', geojson: 'https://example.com/data', idProperty: 'id' },
25
+ 'ds-static-url': { id: 'ds-static-url', geojson: 'https://example.com/static.geojson' },
26
+ // shared: tiles with no zoom — used by source minzoom and maxzoom fallback tests
27
+ 'ds-tiles-no-zoom': { id: 'ds-tiles-no-zoom', tiles: ['https://example.com/{z}/{x}/{y}'] },
28
+ // getSymbolSource/getFillSource/getStrokeSource 'has filter' tests use historic-monuments-prehistoric and existing-fields from demo data
29
+ // _hiddenFeaturesFilter — ds-hf-123 also reused in filter describe
30
+ 'ds-hf-123': { id: 'ds-hf-123', hiddenFeatures: [1, 2, 3] },
31
+ 'ds-hf-neg1': { id: 'ds-hf-neg1', hiddenFeatures: [-1, 5] },
32
+ 'ds-hf-all-neg1': { id: 'ds-hf-all-neg1', hiddenFeatures: [-1] },
33
+ 'ds-hf-empty': { id: 'ds-hf-empty', hiddenFeatures: [] },
34
+ // filter
35
+ 'ds-combined': { id: 'ds-combined', filter: ['==', ['get', 'type'], 'foo'], hiddenFeatures: [5] },
36
+ // _geojsonIdStrategy string-id warning
37
+ 'ds-string-ids': { id: 'ds-string-ids', geojson: { type: 'FeatureCollection', features: [{ type: 'Feature', id: 'feature-1', properties: {}, geometry: null }] } },
38
+ 'ds-integer-ids': { id: 'ds-integer-ids', geojson: { type: 'FeatureCollection', features: [{ type: 'Feature', id: 1, properties: {}, geometry: null }] } }
39
+ })
40
+ })
41
+
42
+ describe('layerIds', () => {
43
+ it('returns [id] when dataset has a symbol (historic-monuments-prehistoric inherits symbol from parent)', () => {
44
+ const dataset = datasetRegistry.getDataset('historic-monuments-prehistoric')
45
+ expect(dataset.layerIds).toEqual(['historic-monuments-prehistoric'])
46
+ })
47
+
48
+ it('returns [id, id-stroke] when dataset has both fill and stroke (existing-fields)', () => {
49
+ const dataset = datasetRegistry.getDataset('existing-fields')
50
+ expect(dataset.layerIds).toEqual(['existing-fields', 'existing-fields-stroke'])
51
+ })
52
+
53
+ it('returns [id] when dataset has only fill', () => {
54
+ const dataset = datasetRegistry.getDataset('ds-fill-only')
55
+ expect(dataset.layerIds).toEqual(['ds-fill-only'])
56
+ })
57
+
58
+ it('returns [id] when dataset has only stroke (hedge-control)', () => {
59
+ const dataset = datasetRegistry.getDataset('hedge-control')
60
+ expect(dataset.layerIds).toEqual(['hedge-control'])
61
+ })
62
+
63
+ it('returns [id] when dataset has a fillPattern but no stroke', () => {
64
+ const dataset = datasetRegistry.getDataset('ds-pattern-only')
65
+ expect(dataset.layerIds).toEqual(['ds-pattern-only'])
66
+ })
67
+
68
+ it('returns [id, id-stroke] when dataset has fillPattern and stroke (land-covers-130-131)', () => {
69
+ const dataset = datasetRegistry.getDataset('land-covers-130-131')
70
+ expect(dataset.layerIds).toEqual(['land-covers-130-131', 'land-covers-130-131-stroke'])
71
+ })
72
+
73
+ it('returns null when fill is transparent and there is no stroke, symbol, or pattern', () => {
74
+ const dataset = datasetRegistry.getDataset('ds-transparent-fill')
75
+ expect(dataset.layerIds).toEqual([])
76
+ })
77
+
78
+ it('returns null when dataset has no fill, stroke, symbol, or pattern', () => {
79
+ const dataset = datasetRegistry.getDataset('ds-bare')
80
+ expect(dataset.layerIds).toEqual([])
81
+ })
82
+
83
+ it('returns the combined layerIds from all sublayers (historic-monuments)', () => {
84
+ const dataset = datasetRegistry.getDataset('historic-monuments')
85
+ expect(dataset.layerIds).toEqual([
86
+ 'historic-monuments-prehistoric',
87
+ 'historic-monuments-roman',
88
+ 'historic-monuments-medieval'
89
+ ])
90
+ })
91
+
92
+ it('returns the combined layerIds from all sublayers (land-covers)', () => {
93
+ const dataset = datasetRegistry.getDataset('land-covers')
94
+ expect(dataset.layerIds).toEqual([
95
+ 'land-covers-130-131', 'land-covers-130-131-stroke',
96
+ 'land-covers-332', 'land-covers-332-stroke',
97
+ 'land-covers-110', 'land-covers-110-stroke',
98
+ 'land-covers-379', 'land-covers-379-stroke',
99
+ 'land-covers-other', 'land-covers-other-stroke'
100
+ ])
101
+ })
102
+ })
103
+
104
+ describe('visibility', () => {
105
+ it('returns "visible" when visible is true', () => {
106
+ expect(datasetRegistry.getDataset('existing-fields').visibility).toBe('visible')
107
+ })
108
+
109
+ it('returns "none" when visible is false', () => {
110
+ expect(datasetRegistry.getDataset('hedge-control').visibility).toBe('none')
111
+ })
112
+ })
113
+
114
+ describe('getLayersWithVisibility', () => {
115
+ it('returns layerIds and visibility for a layer with sublayers', () => {
116
+ const registryDataset = datasetRegistry.getDataset('land-covers')
117
+ const layersWithVisibility = registryDataset.getLayersWithVisibility()
118
+ expect(layersWithVisibility).toEqual([
119
+ { layerIds: ['land-covers-130-131', 'land-covers-130-131-stroke'], visibility: 'visible' },
120
+ { layerIds: ['land-covers-332', 'land-covers-332-stroke'], visibility: 'visible' },
121
+ { layerIds: ['land-covers-110', 'land-covers-110-stroke'], visibility: 'visible' },
122
+ { layerIds: ['land-covers-379', 'land-covers-379-stroke'], visibility: 'none' },
123
+ { layerIds: ['land-covers-other', 'land-covers-other-stroke'], visibility: 'visible' }
124
+ ])
125
+ })
126
+ })
127
+
128
+ describe('getLayersWithOpacity', () => {
129
+ it('returns layerIds and opacity for a sublayer with no sublayers', () => {
130
+ const dataset = datasetRegistry.getDataset('existing-fields')
131
+ expect(dataset.getLayersWithOpacity()).toEqual([{ layerIds: ['existing-fields', 'existing-fields-stroke'], opacity: 1 }])
132
+ })
133
+
134
+ it('returns layerIds and opacity for a sublayer', () => {
135
+ const dataset = datasetRegistry.getDataset('land-covers-130-131')
136
+ const result = dataset.getLayersWithOpacity()
137
+ expect(result).toEqual([{ layerIds: ['land-covers-130-131', 'land-covers-130-131-stroke'], opacity: 1 }])
138
+ })
139
+
140
+ it('returns layerIds and opacity for a sublayer with sublayers with specific opacity', () => {
141
+ const parentDef = { id: 'parent-ds', sublayerIds: ['parent-ds-sub'] }
142
+ const subDef = { id: 'parent-ds-sub', parentId: 'parent-ds', style: { stroke: '#ff0000', fill: '#00ff00', opacity: 0.75 } }
143
+ datasetRegistry.attach({ 'parent-ds': parentDef, 'parent-ds-sub': subDef })
144
+ const dataset = datasetRegistry.getDataset('parent-ds')
145
+ const result = dataset.getLayersWithOpacity()
146
+ expect(result).toEqual([{ layerIds: ['parent-ds-sub', 'parent-ds-sub-stroke'], opacity: 0.75 }])
147
+ })
148
+
149
+ it('returns layerIds and opacity for all sublayers', () => {
150
+ const dataset = datasetRegistry.getDataset('historic-monuments')
151
+ expect(dataset.getLayersWithOpacity()).toEqual([
152
+ { layerIds: ['historic-monuments-prehistoric'], opacity: 1 },
153
+ { layerIds: ['historic-monuments-roman'], opacity: 1 },
154
+ { layerIds: ['historic-monuments-medieval'], opacity: 1 }
155
+ ])
156
+ })
157
+ })
158
+
159
+ describe('fillLayerId, strokeLayerId, symbolLayerId — hasSublayers returns null', () => {
160
+ it('fillLayerId returns null for a dataset with sublayers', () => {
161
+ const dataset = datasetRegistry.getDataset('land-covers')
162
+ expect(dataset.fillLayerId).toBeNull()
163
+ })
164
+
165
+ it('strokeLayerId returns null for a dataset with sublayers', () => {
166
+ const dataset = datasetRegistry.getDataset('land-covers')
167
+ expect(dataset.strokeLayerId).toBeNull()
168
+ })
169
+
170
+ it('symbolLayerId returns null for a dataset with sublayers', () => {
171
+ const dataset = datasetRegistry.getDataset('land-covers')
172
+ expect(dataset.symbolLayerId).toBeNull()
173
+ })
174
+ })
175
+
176
+ describe('getLayersWithFilters', () => {
177
+ it('returns an empty array when there are no hidden features and no sublayers', () => {
178
+ const dataset = datasetRegistry.getDataset('existing-fields')
179
+ expect(dataset.getLayersWithFilters()).toEqual([])
180
+ })
181
+
182
+ it('returns an entry with layerIds and filter when the dataset has hidden features', () => {
183
+ const dataset = datasetRegistry.getDataset('land-covers-130-131')
184
+ expect(dataset.getLayersWithFilters()).toEqual([{
185
+ layerIds: ['land-covers-130-131', 'land-covers-130-131-stroke'],
186
+ filter: ['all',
187
+ ['!', ['in', ['to-string', ['get', 'id']], ['literal', ['42']]]],
188
+ ['in', ['get', 'dominant_land_cover'], ['literal', ['130', '131']]]]
189
+ }])
190
+ })
191
+
192
+ it('includes sublayer entries when a sublayer has hidden features', () => {
193
+ const parentDef = { id: 'parent-ds', sublayerIds: ['parent-ds-sub'] }
194
+ const subDef = { id: 'parent-ds-sub', parentId: 'parent-ds', hiddenFeatures: [7], style: { stroke: '#ff0000', fill: '#00ff00' } }
195
+ datasetRegistry.attach({ 'parent-ds': parentDef, 'parent-ds-sub': subDef })
196
+ const dataset = datasetRegistry.getDataset('parent-ds')
197
+ expect(dataset.getLayersWithFilters()).toEqual([{
198
+ layerIds: ['parent-ds-sub', 'parent-ds-sub-stroke'],
199
+ filter: ['!', ['in', ['to-string', ['id']], ['literal', ['7']]]]
200
+ }])
201
+ })
202
+
203
+ it('returns an empty array when sublayers exist but none have hidden features', () => {
204
+ const dataset = datasetRegistry.getDataset('historic-monuments')
205
+ expect(dataset.getLayersWithFilters()).toEqual([])
206
+ })
207
+ })
208
+
209
+ describe('sourceId', () => {
210
+ it('returns the parent sourceId for a sublayer', () => {
211
+ const sublayer = datasetRegistry.getDataset('land-covers-130-131')
212
+ expect(sublayer.sourceId).toBe(datasetRegistry.getDataset('land-covers').sourceId)
213
+ })
214
+
215
+ it('returns a tiles-based id for a tile dataset (array tiles)', () => {
216
+ const dataset = datasetRegistry.getDataset('existing-fields')
217
+ expect(dataset.sourceId).toBe('tiles-3delfuv')
218
+ })
219
+
220
+ it('returns a tiles-based id when tiles is a plain string (line 76 non-array branch)', () => {
221
+ const dataset = datasetRegistry.getDataset('existing-fields')
222
+ expect(dataset.sourceId).toBe('tiles-3delfuv')
223
+ })
224
+
225
+ it('returns geojson-dynamic-{id} for a dynamic geojson source', () => {
226
+ expect(datasetRegistry.getDataset('land-covers').sourceId).toBe('geojson-dynamic-land-covers')
227
+ })
228
+
229
+ it('returns geojson-{hash} for a static string geojson url', () => {
230
+ expect(datasetRegistry.getDataset('ds-static-url').sourceId).toBe('geojson-1u4xay')
231
+ })
232
+
233
+ it('returns geojson-{id} for an object geojson source', () => {
234
+ const dataset = datasetRegistry.getDataset('historic-monuments')
235
+ expect(dataset.sourceId).toBe('geojson-historic-monuments')
236
+ })
237
+
238
+ it('returns source-{id} when there are no tiles and no geojson', () => {
239
+ expect(datasetRegistry.getDataset('ds-bare').sourceId).toBe('source-ds-bare')
240
+ })
241
+ })
242
+
243
+ describe('source', () => {
244
+ it('returns a vector source for a tile dataset', () => {
245
+ expect(datasetRegistry.getDataset('existing-fields').source).toEqual({
246
+ type: 'vector',
247
+ tiles: 'https://farming-tiles-702a60f45633.herokuapp.com/field_parcels_with_hedges/{z}/{x}/{y}',
248
+ minzoom: 10,
249
+ maxzoom: 24
250
+ })
251
+ })
252
+
253
+ it('falls back to 0/22 for minzoom/maxzoom when zoom is not set', () => {
254
+ expect(datasetRegistry.getDataset('ds-tiles-no-zoom').source).toEqual({
255
+ type: 'vector',
256
+ tiles: ['https://example.com/{z}/{x}/{y}'],
257
+ minzoom: 0,
258
+ maxzoom: 22
259
+ })
260
+ })
261
+
262
+ it('returns a geojson source with promoteId when the dynamic source has idProperty', () => {
263
+ expect(datasetRegistry.getDataset('land-covers').source).toEqual({
264
+ type: 'geojson',
265
+ data: { type: 'FeatureCollection', features: [] },
266
+ promoteId: 'id'
267
+ })
268
+ })
269
+
270
+ it('returns a geojson source with promoteId when static geojson has idProperty', () => {
271
+ expect(datasetRegistry.getDataset('ds-no-transform').source).toEqual({
272
+ type: 'geojson',
273
+ data: 'https://example.com/data',
274
+ promoteId: 'id'
275
+ })
276
+ })
277
+
278
+ it('returns a geojson source with generateId when generateIds is true', () => {
279
+ datasetRegistry.mockExtend({ 'ds-generate-ids': { id: 'ds-generate-ids', geojson: 'https://example.com/data', generateIds: true } })
280
+ expect(datasetRegistry.getDataset('ds-generate-ids').source).toEqual({
281
+ type: 'geojson',
282
+ data: 'https://example.com/data',
283
+ generateId: true
284
+ })
285
+ })
286
+
287
+ it('returns a plain geojson source with no id strategy when neither idProperty nor generateIds is set', () => {
288
+ const dataset = datasetRegistry.getDataset('historic-monuments')
289
+ expect(dataset.source).toEqual({ type: 'geojson', data: dataset.geojson })
290
+ })
291
+
292
+ it('returns null when there are no tiles and no geojson', () => {
293
+ expect(datasetRegistry.getDataset('ds-bare').source).toBeNull()
294
+ })
295
+
296
+ it('warns when geojson features have string native IDs and no id strategy is set', () => {
297
+ expect(datasetRegistry.getDataset('ds-string-ids').source).toEqual(expect.objectContaining({ type: 'geojson' }))
298
+ expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('"ds-string-ids"'))
299
+ expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('string native IDs'))
300
+ })
301
+
302
+ it('does not warn when geojson features have integer native IDs', () => {
303
+ expect(datasetRegistry.getDataset('ds-integer-ids').source).toEqual(expect.objectContaining({ type: 'geojson' }))
304
+ expect(logger.warn).not.toHaveBeenCalled()
305
+ })
306
+
307
+ it('does not warn when idProperty is set even if features have string native IDs', () => {
308
+ datasetRegistry.mockExtend({ 'ds-string-with-prop': { id: 'ds-string-with-prop', idProperty: 'myId', geojson: { type: 'FeatureCollection', features: [{ type: 'Feature', id: 'f-1', properties: { myId: 1 }, geometry: null }] } } })
309
+ expect(datasetRegistry.getDataset('ds-string-with-prop').source).toEqual(expect.objectContaining({ type: 'geojson' }))
310
+ expect(logger.warn).not.toHaveBeenCalled()
311
+ })
312
+ })
313
+
314
+ describe('getSymbolSource', () => {
315
+ const prehistoricBase = {
316
+ id: 'historic-monuments-prehistoric',
317
+ type: 'symbol',
318
+ source: 'geojson-historic-monuments',
319
+ 'source-layer': undefined,
320
+ minzoom: 6, // datasetDefaults.minZoom applied to sublayer (parent's 10 is not inherited)
321
+ maxzoom: 24,
322
+ filter: ['in', ['get', 'category'], 'prehistoric']
323
+ }
324
+
325
+ it('returns a symbol layer spec with the correct shape', () => {
326
+ expect(datasetRegistry.getDataset('historic-monuments-prehistoric').getSymbolSource('my-icon', null, null)).toEqual({
327
+ ...prehistoricBase,
328
+ layout: { visibility: 'visible', 'icon-image': 'my-icon', 'icon-anchor': 'center', 'icon-allow-overlap': true }
329
+ })
330
+ })
331
+
332
+ it('uses the provided anchor when given', () => {
333
+ expect(datasetRegistry.getDataset('historic-monuments-prehistoric').getSymbolSource('icon', [0.1, 0.9], null)).toEqual({
334
+ ...prehistoricBase,
335
+ layout: { visibility: 'visible', 'icon-image': 'icon', 'icon-anchor': 'bottom-left', 'icon-allow-overlap': true }
336
+ })
337
+ })
338
+
339
+ it('falls back to symbolDef.anchor when no anchor is provided', () => {
340
+ expect(datasetRegistry.getDataset('historic-monuments-prehistoric').getSymbolSource('icon', null, { anchor: [0.5, 0] })).toEqual({
341
+ ...prehistoricBase,
342
+ layout: { visibility: 'visible', 'icon-image': 'icon', 'icon-anchor': 'top', 'icon-allow-overlap': true }
343
+ })
344
+ })
345
+
346
+ it('does not include a filter property when filter is null', () => {
347
+ expect(datasetRegistry.getDataset('historic-monuments').getSymbolSource('icon', null, null)).toEqual({
348
+ id: null,
349
+ type: 'symbol',
350
+ source: 'geojson-historic-monuments',
351
+ 'source-layer': undefined,
352
+ minzoom: 10,
353
+ maxzoom: 24,
354
+ layout: { visibility: 'visible', 'icon-image': 'icon', 'icon-anchor': 'center', 'icon-allow-overlap': true }
355
+ })
356
+ })
357
+
358
+ it('includes a filter property when the dataset has a filter', () => {
359
+ expect(datasetRegistry.getDataset('historic-monuments-prehistoric').getSymbolSource('icon', null, null)).toEqual({
360
+ ...prehistoricBase,
361
+ layout: { visibility: 'visible', 'icon-image': 'icon', 'icon-anchor': 'center', 'icon-allow-overlap': true }
362
+ })
363
+ })
364
+ })
365
+
366
+ describe('getFillSource', () => {
367
+ const fillBase = {
368
+ id: 'ds-fill-only',
369
+ type: 'fill',
370
+ source: 'source-ds-fill-only',
371
+ 'source-layer': undefined,
372
+ minzoom: 10,
373
+ maxzoom: 24,
374
+ layout: { visibility: 'visible' }
375
+ }
376
+
377
+ it('returns a fill layer spec with the correct shape', () => {
378
+ expect(datasetRegistry.getDataset('ds-fill-only').getFillSource({ 'fill-color': '#ff0000' })).toEqual({
379
+ ...fillBase,
380
+ paint: { 'fill-color': '#ff0000' }
381
+ })
382
+ })
383
+
384
+ it('does not include a filter property when filter is null', () => {
385
+ expect(datasetRegistry.getDataset('ds-fill-only').getFillSource({})).toEqual({ ...fillBase, paint: {} })
386
+ })
387
+
388
+ it('includes a filter property when the dataset has a filter', () => {
389
+ expect(datasetRegistry.getDataset('existing-fields').getFillSource({})).toEqual({
390
+ id: 'existing-fields',
391
+ type: 'fill',
392
+ source: 'tiles-3delfuv',
393
+ 'source-layer': 'field_parcels_filtered',
394
+ minzoom: 10,
395
+ maxzoom: 24,
396
+ layout: { visibility: 'visible' },
397
+ paint: {},
398
+ filter: ['all', ['==', ['get', 'sbi'], '106223377'], ['==', ['get', 'is_dominant_land_cover'], true]]
399
+ })
400
+ })
401
+ })
402
+
403
+ describe('getStrokeSource', () => {
404
+ it('returns a line layer spec with the correct shape', () => {
405
+ expect(datasetRegistry.getDataset('hedge-control')
406
+ .getStrokeSource({ 'line-color': '#ff0000', 'line-width': 2 }))
407
+ .toEqual({
408
+ id: 'hedge-control',
409
+ type: 'line',
410
+ source: 'tiles-3delfuv',
411
+ 'source-layer': 'hedge_control',
412
+ minzoom: 10,
413
+ maxzoom: 24,
414
+ layout: { visibility: 'none' },
415
+ paint: { 'line-color': '#ff0000', 'line-width': 2 }
416
+ })
417
+ })
418
+
419
+ it('includes a filter property when the dataset has a filter', () => {
420
+ expect(datasetRegistry.getDataset('existing-fields')
421
+ .getStrokeSource({}))
422
+ .toEqual({
423
+ id: 'existing-fields-stroke',
424
+ type: 'line',
425
+ source: 'tiles-3delfuv',
426
+ 'source-layer': 'field_parcels_filtered',
427
+ minzoom: 10,
428
+ maxzoom: 24,
429
+ layout: { visibility: 'visible' },
430
+ paint: {},
431
+ filter: ['all', ['==', ['get', 'sbi'], '106223377'], ['==', ['get', 'is_dominant_land_cover'], true]]
432
+ })
433
+ })
434
+ })
435
+
436
+ describe('_hiddenFeaturesIdExpression', () => {
437
+ it('uses get(idProperty) when idProperty is set', () => {
438
+ expect(datasetRegistry.getDataset('land-covers')._hiddenFeaturesIdExpression).toEqual(['to-string', ['get', 'id']])
439
+ })
440
+
441
+ it('uses the feature id when idProperty is not set', () => {
442
+ expect(datasetRegistry.getDataset('ds-bare')._hiddenFeaturesIdExpression).toEqual(['to-string', ['id']])
443
+ })
444
+ })
445
+
446
+ describe('filter', () => {
447
+ it('returns null when there is no filter and no hidden features', () => {
448
+ expect(datasetRegistry.getDataset('ds-bare').filter).toBeNull()
449
+ })
450
+
451
+ it('returns the own filter directly when it is the only filter', () => {
452
+ expect(datasetRegistry.getDataset('historic-monuments-prehistoric').filter)
453
+ .toEqual(['in', ['get', 'category'], 'prehistoric'])
454
+ })
455
+
456
+ it('returns ["all", parentFilter, ownFilter] when both parent and own filter are present', () => {
457
+ const parentFilter = ['==', ['get', 'cat'], 'a']
458
+ const childFilter = ['==', ['get', 'sub'], 'b']
459
+ const parentDef = { id: 'p-filter', filter: parentFilter, sublayerIds: ['p-filter-child'] }
460
+ const childDef = { id: 'p-filter-child', parentId: 'p-filter', filter: childFilter }
461
+ datasetRegistry.attach({ 'p-filter': parentDef, 'p-filter-child': childDef })
462
+ const dataset = datasetRegistry.getDataset('p-filter-child')
463
+ expect(dataset.filter).toEqual(['all', parentFilter, childFilter])
464
+ })
465
+
466
+ it('returns the hidden features filter directly when it is the only filter', () => {
467
+ expect(datasetRegistry.getDataset('ds-hf-123').filter)
468
+ .toEqual(['!', ['in', ['to-string', ['id']], ['literal', ['1', '2', '3']]]])
469
+ })
470
+
471
+ it('returns ["all", ...] when both own filter and hidden features filter are present', () => {
472
+ const { filter } = datasetRegistry.getDataset('ds-combined')
473
+ expect(filter).toEqual([
474
+ 'all',
475
+ ['==', ['get', 'type'], 'foo'],
476
+ [
477
+ '!',
478
+ ['in', ['to-string', ['id']], ['literal', ['5']]]
479
+ ]
480
+ ])
481
+ })
482
+ })
483
+ })
@@ -1,6 +1,3 @@
1
- import { datasetDefaults } from '../defaults.js'
2
-
3
- export const addDataset = ({ pluginState, mapState }, dataset) => {
4
- pluginState.layerAdapter?.addDataset({ ...datasetDefaults, ...dataset }, mapState.mapStyle)
5
- pluginState.dispatch({ type: 'ADD_DATASET', payload: { dataset, datasetDefaults } })
1
+ export const addDataset = ({ pluginState: { dispatch }, mapState }, dataset) => {
2
+ dispatch({ type: 'ADD_DATASET', payload: { dataset, mapStyle: mapState.mapStyle } })
6
3
  }
@@ -0,0 +1,16 @@
1
+ import { addDataset } from './addDataset.js'
2
+
3
+ describe('addDataset', () => {
4
+ it('dispatches ADD_DATASET with the dataset and mapStyle', () => {
5
+ const dispatch = jest.fn()
6
+ const dataset = { id: 'my-dataset', label: 'My Dataset' }
7
+ const mapState = { mapStyle: { version: 8, layers: [] } }
8
+
9
+ addDataset({ pluginState: { dispatch }, mapState }, dataset)
10
+
11
+ expect(dispatch).toHaveBeenCalledWith({
12
+ type: 'ADD_DATASET',
13
+ payload: { dataset, mapStyle: mapState.mapStyle }
14
+ })
15
+ })
16
+ })
@@ -1,17 +1,17 @@
1
- export const getOpacity = ({ pluginState }, options) => {
2
- const { datasetId, sublayerId } = options || {}
1
+ import { logger } from '../../../../../src/services/logger.js'
2
+ import { datasetRegistry } from '../registry/datasetRegistry.js'
3
3
 
4
- if (sublayerId) {
5
- const dataset = pluginState.datasets?.find(d => d.id === datasetId)
6
- const sublayer = dataset?.sublayers?.find(s => s.id === sublayerId)
7
- return sublayer?.style?.opacity ?? null
4
+ export const getOpacity = ({ pluginState: { globals } }, options = {}) => {
5
+ const { datasetId, sublayerId } = options
6
+ const fullId = sublayerId ? `${datasetId}-${sublayerId}` : datasetId
7
+ if (fullId) {
8
+ const registryDataset = datasetRegistry.getDataset(fullId)
9
+ if (!registryDataset) {
10
+ logger.warn(`getOpacity: Dataset with id ${fullId} not found`)
11
+ return null
12
+ }
13
+ return registryDataset.opacity ?? null
8
14
  }
9
-
10
- if (datasetId) {
11
- const dataset = pluginState.datasets?.find(d => d.id === datasetId)
12
- return dataset?.opacity ?? null
13
- }
14
-
15
- // Global — return first dataset's opacity
16
- return pluginState.datasets?.[0]?.opacity ?? null
15
+ // Global
16
+ return globals.opacity ?? null
17
17
  }
@@ -0,0 +1,72 @@
1
+ import { getOpacity } from './getOpacity.js'
2
+ import { datasetRegistry } from '../registry/datasetRegistry.js'
3
+ import { logger } from '../../../../../src/services/logger.js'
4
+
5
+ jest.mock('../registry/datasetRegistry.js', () => ({
6
+ datasetRegistry: { getDataset: jest.fn() }
7
+ }))
8
+
9
+ jest.mock('../../../../../src/services/logger.js', () => ({
10
+ logger: { warn: jest.fn() }
11
+ }))
12
+
13
+ describe('getOpacity', () => {
14
+ beforeEach(() => {
15
+ jest.clearAllMocks()
16
+ })
17
+
18
+ describe('global (no datasetId)', () => {
19
+ it('returns globals.opacity when set', () => {
20
+ const ctx = { pluginState: { globals: { opacity: 0.5 } } }
21
+ expect(getOpacity(ctx)).toBe(0.5)
22
+ })
23
+
24
+ it('returns null when globals.opacity is undefined', () => {
25
+ const ctx = { pluginState: { globals: {} } }
26
+ expect(getOpacity(ctx)).toBeNull()
27
+ })
28
+ })
29
+
30
+ describe('with datasetId', () => {
31
+ it('returns opacity from the registry dataset', () => {
32
+ datasetRegistry.getDataset.mockReturnValue({ opacity: 0.7 })
33
+ const ctx = { pluginState: { globals: {} } }
34
+ expect(getOpacity(ctx, { datasetId: 'my-dataset' })).toBe(0.7)
35
+ })
36
+
37
+ it('returns null when the dataset has no opacity property', () => {
38
+ datasetRegistry.getDataset.mockReturnValue({})
39
+ const ctx = { pluginState: { globals: {} } }
40
+ expect(getOpacity(ctx, { datasetId: 'my-dataset' })).toBeNull()
41
+ })
42
+
43
+ it('returns null and warns when the dataset is not found', () => {
44
+ datasetRegistry.getDataset.mockReturnValue(undefined)
45
+ const ctx = { pluginState: { globals: {} } }
46
+ expect(getOpacity(ctx, { datasetId: 'missing' })).toBeNull()
47
+ expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('missing'))
48
+ })
49
+ })
50
+
51
+ describe('with sublayerId', () => {
52
+ it('combines datasetId and sublayerId for the registry lookup', () => {
53
+ datasetRegistry.getDataset.mockReturnValue({ opacity: 0.3 })
54
+ const ctx = { pluginState: { globals: {} } }
55
+ getOpacity(ctx, { datasetId: 'parent', sublayerId: 'child' })
56
+ expect(datasetRegistry.getDataset).toHaveBeenCalledWith('parent-child')
57
+ })
58
+
59
+ it('returns opacity for the combined id', () => {
60
+ datasetRegistry.getDataset.mockReturnValue({ opacity: 0.3 })
61
+ const ctx = { pluginState: { globals: {} } }
62
+ expect(getOpacity(ctx, { datasetId: 'parent', sublayerId: 'child' })).toBe(0.3)
63
+ })
64
+
65
+ it('returns null and warns when the sublayer dataset is not found', () => {
66
+ datasetRegistry.getDataset.mockReturnValue(undefined)
67
+ const ctx = { pluginState: { globals: {} } }
68
+ expect(getOpacity(ctx, { datasetId: 'parent', sublayerId: 'missing' })).toBeNull()
69
+ expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('parent-missing'))
70
+ })
71
+ })
72
+ })
@@ -1,13 +1,12 @@
1
+ import { logger } from '../../../../../src/services/logger.js'
2
+ import { datasetRegistry } from '../registry/datasetRegistry.js'
3
+
1
4
  export const getStyle = ({ pluginState }, { datasetId, sublayerId } = {}) => {
2
- const dataset = pluginState.datasets?.find(d => d.id === datasetId)
3
- if (!dataset) {
5
+ datasetId = sublayerId ? `${datasetId}-${sublayerId}` : datasetId
6
+ const registryDataset = datasetRegistry.getDataset(datasetId)
7
+ if (!registryDataset) {
8
+ logger.warn(`getStyle: Dataset with id ${datasetId} not found`)
4
9
  return null
5
10
  }
6
-
7
- if (sublayerId) {
8
- const sublayer = dataset.sublayers?.find(s => s.id === sublayerId)
9
- return sublayer?.style ?? null
10
- }
11
-
12
- return dataset.style ?? null
11
+ return registryDataset.style
13
12
  }