@defra/interactive-map 0.0.30-alpha → 0.0.31-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 (192) hide show
  1. package/dist/css/index.css +1 -1
  2. package/dist/esm/im-core.js +1 -1
  3. package/dist/esm/im-shell.js +1 -1
  4. package/dist/umd/im-core.js +1 -1
  5. package/dist/umd/index.js +1 -1
  6. package/docs/api/context.md +4 -4
  7. package/docs/api.md +111 -37
  8. package/docs/assets/css/docusaurus.scss +20 -4
  9. package/docs/assets/images/basic-map.jpg +0 -0
  10. package/docs/assets/images/button-first.jpg +0 -0
  11. package/docs/assets/images/deuteranopia-ozs-map-thumb.jpg +0 -0
  12. package/docs/assets/images/draw.jpg +0 -0
  13. package/docs/assets/images/marker-panel.jpg +0 -0
  14. package/docs/assets/images/night-ozs-map-thumb.jpg +0 -0
  15. package/docs/assets/images/outdoor-ozs-map-thumb.jpg +0 -0
  16. package/docs/assets/images/search.jpg +0 -0
  17. package/docs/assets/images/select-feature.jpg +0 -0
  18. package/docs/assets/images/style-switcher.jpg +0 -0
  19. package/docs/assets/images/toggle-marker-label.jpg +0 -0
  20. package/docs/assets/images/tritanopia-ozs-map-thumb.jpg +0 -0
  21. package/docs/examples/add-marker-with-panel.mdx +2 -2
  22. package/docs/examples/basic-map.mdx +2 -2
  23. package/docs/examples/draw.mdx +271 -0
  24. package/docs/examples/index.mdx +58 -10
  25. package/docs/examples/search-control.mdx +71 -0
  26. package/docs/examples/select-feature.mdx +146 -0
  27. package/docs/examples/style-switcher.mdx +135 -0
  28. package/docs/examples/toggle-marker-label.mdx +3 -3
  29. package/docs/plugins/datasets.md +15 -28
  30. package/docusaurus.config.cjs +5 -1
  31. package/package.json +1 -1
  32. package/plugins/beta/datasets/dist/css/index.css +4 -0
  33. package/plugins/beta/datasets/dist/esm/im-datasets-ml-adapter.js +1 -0
  34. package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +1 -1
  35. package/plugins/beta/datasets/dist/esm/index.js +1 -1
  36. package/plugins/beta/datasets/dist/umd/im-datasets-ml-adapter.js +2 -0
  37. package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +2 -0
  38. package/plugins/beta/datasets/dist/umd/index.js +2 -0
  39. package/plugins/beta/datasets/src/adapters/loadLayerAdapter.js +33 -0
  40. package/plugins/beta/datasets/src/adapters/maplibre/layerBuilders.js +44 -120
  41. package/plugins/beta/datasets/src/adapters/maplibre/maplibreLayerAdapter.js +145 -245
  42. package/plugins/beta/datasets/src/adapters/maplibre/maplibreLayerAdapter.test.js +516 -0
  43. package/plugins/beta/datasets/src/adapters/maplibre/registry/mapLibreDataset.js +191 -0
  44. package/plugins/beta/datasets/src/adapters/maplibre/registry/mapLibreDataset.test.js +441 -0
  45. package/plugins/beta/datasets/src/api/addDataset.js +2 -5
  46. package/plugins/beta/datasets/src/api/addDataset.test.js +16 -0
  47. package/plugins/beta/datasets/src/api/getOpacity.js +14 -14
  48. package/plugins/beta/datasets/src/api/getOpacity.test.js +72 -0
  49. package/plugins/beta/datasets/src/api/getStyle.js +8 -9
  50. package/plugins/beta/datasets/src/api/getStyle.test.js +42 -0
  51. package/plugins/beta/datasets/src/api/removeDataset.js +7 -7
  52. package/plugins/beta/datasets/src/api/removeDataset.test.js +45 -0
  53. package/plugins/beta/datasets/src/api/setData.js +9 -3
  54. package/plugins/beta/datasets/src/api/setData.test.js +62 -0
  55. package/plugins/beta/datasets/src/api/setDatasetVisibility.js +5 -30
  56. package/plugins/beta/datasets/src/api/setDatasetVisibility.test.js +31 -0
  57. package/plugins/beta/datasets/src/api/setFeatureVisibility.js +3 -21
  58. package/plugins/beta/datasets/src/api/setFeatureVisibility.test.js +32 -0
  59. package/plugins/beta/datasets/src/api/setGlobals.js +20 -0
  60. package/plugins/beta/datasets/src/api/setGlobals.test.js +63 -0
  61. package/plugins/beta/datasets/src/api/setOpacity.js +8 -27
  62. package/plugins/beta/datasets/src/api/setOpacity.test.js +31 -0
  63. package/plugins/beta/datasets/src/api/setStyle.js +5 -21
  64. package/plugins/beta/datasets/src/api/setStyle.test.js +53 -0
  65. package/plugins/beta/datasets/src/components/Key/Key.jsx +51 -0
  66. package/plugins/beta/datasets/src/components/Key/KeyGroupItem.jsx +18 -0
  67. package/plugins/beta/datasets/src/components/Key/KeyItem.jsx +20 -0
  68. package/plugins/beta/datasets/src/components/Key/KeySvg.jsx +22 -0
  69. package/plugins/beta/datasets/src/components/Key/KeySvg.test.jsx +87 -0
  70. package/plugins/beta/datasets/src/components/Key/KeySvgLine.jsx +19 -0
  71. package/plugins/beta/datasets/src/components/Key/KeySvgLine.test.jsx +71 -0
  72. package/plugins/beta/datasets/src/components/{KeySvgPattern.jsx → Key/KeySvgPattern.jsx} +3 -4
  73. package/plugins/beta/datasets/src/components/Key/KeySvgPattern.test.jsx +55 -0
  74. package/plugins/beta/datasets/src/components/Key/KeySvgRect.jsx +22 -0
  75. package/plugins/beta/datasets/src/components/Key/KeySvgRect.test.jsx +85 -0
  76. package/plugins/beta/datasets/src/components/{KeySvgSymbol.jsx → Key/KeySvgSymbol.jsx} +5 -5
  77. package/plugins/beta/datasets/src/components/Key/KeySvgSymbol.test.jsx +80 -0
  78. package/plugins/beta/datasets/src/components/Key/svgProperties.test.js +61 -0
  79. package/plugins/beta/datasets/src/{panels → components/LayersMenu}/Layers.module.scss +4 -0
  80. package/plugins/beta/datasets/src/components/LayersMenu/LayersMenu.jsx +33 -0
  81. package/plugins/beta/datasets/src/components/LayersMenu/LayersMenuCheckbox.jsx +31 -0
  82. package/plugins/beta/datasets/src/components/LayersMenu/LayersMenuGroupWrapper.jsx +19 -0
  83. package/plugins/beta/datasets/src/datasets.scss +2 -2
  84. package/plugins/beta/datasets/src/fetch/createDynamicSource.js +8 -8
  85. package/plugins/beta/datasets/src/{DatasetsInit.jsx → initialise/DatasetsInit.jsx} +19 -21
  86. package/plugins/beta/datasets/src/{defaults.js → initialise/defaults.js} +17 -2
  87. package/plugins/beta/datasets/src/initialise/defaults.test.js +127 -0
  88. package/plugins/beta/datasets/src/{datasets.js → initialise/initialiseDatasets.js} +30 -26
  89. package/plugins/beta/datasets/src/initialise/initialiseDatasets.test.js +273 -0
  90. package/plugins/beta/datasets/src/manifest.js +8 -6
  91. package/plugins/beta/datasets/src/reducers/__data__/demoDatasets.js +253 -0
  92. package/plugins/beta/datasets/src/reducers/datasetsToMenu.js +78 -0
  93. package/plugins/beta/datasets/src/reducers/datasetsToMenu.test.js +35 -0
  94. package/plugins/beta/datasets/src/reducers/mappedDatasetsReducer.js +34 -0
  95. package/plugins/beta/datasets/src/reducers/mappedDatasetsReducer.test.js +117 -0
  96. package/plugins/beta/datasets/src/reducers/pluginState.js +206 -0
  97. package/plugins/beta/datasets/src/reducers/pluginState.test.js +386 -0
  98. package/plugins/beta/datasets/src/registry/__mocks__/datasetRegistry.js +29 -0
  99. package/plugins/beta/datasets/src/registry/createDataset.js +5 -0
  100. package/plugins/beta/datasets/src/registry/dataset.js +172 -0
  101. package/plugins/beta/datasets/src/registry/dataset.test.js +483 -0
  102. package/plugins/beta/datasets/src/registry/datasetDefinitionCache.js +35 -0
  103. package/plugins/beta/datasets/src/registry/datasetRegistry.js +116 -0
  104. package/plugins/beta/datasets/src/registry/datasetRegistry.test.js +335 -0
  105. package/plugins/beta/datasets/src/registry/dynamicGeoJson.js +17 -0
  106. package/plugins/beta/datasets/src/registry/globalDataset.js +36 -0
  107. package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
  108. package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
  109. package/plugins/beta/draw-ml/src/events.js +13 -1
  110. package/plugins/beta/draw-ml/src/index.js +1 -1
  111. package/plugins/beta/draw-ml/src/manifest.js +3 -0
  112. package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
  113. package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
  114. package/plugins/interact/src/InteractInit.jsx +2 -2
  115. package/plugins/interact/src/InteractInit.test.js +1 -1
  116. package/plugins/interact/src/events.js +2 -29
  117. package/plugins/interact/src/events.test.js +4 -102
  118. package/plugins/interact/src/hooks/useAttachEvents.js +3 -4
  119. package/plugins/interact/src/hooks/useAttachEvents.test.js +1 -3
  120. package/plugins/interact/src/hooks/useInteractionHandlers.js +10 -1
  121. package/plugins/interact/src/hooks/useInteractionHandlers.test.js +23 -4
  122. package/plugins/interact/src/manifest.js +0 -35
  123. package/plugins/interact/src/manifest.test.js +3 -43
  124. package/providers/beta/esri/dist/esm/im-esri-provider.js +1 -1
  125. package/providers/beta/esri/src/esriProvider.js +4 -0
  126. package/providers/beta/openlayers/dist/esm/im-openlayers-provider.js +1 -1
  127. package/providers/beta/openlayers/dist/umd/im-openlayers-provider.js +1 -1
  128. package/providers/beta/openlayers/src/openlayersProvider.js +4 -0
  129. package/providers/beta/openlayers/src/openlayersProvider.test.js +5 -0
  130. package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
  131. package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
  132. package/providers/maplibre/src/maplibreProvider.js +6 -4
  133. package/providers/maplibre/src/maplibreProvider.test.js +5 -0
  134. package/providers/maplibre/src/utils/highlightFeatures.js +74 -25
  135. package/providers/maplibre/src/utils/highlightFeatures.test.js +132 -0
  136. package/providers/maplibre/src/utils/patternImages.js +2 -4
  137. package/providers/maplibre/src/utils/symbolImages.js +1 -4
  138. package/rollup.esm.mjs +2 -8
  139. package/src/App/components/MapButton/MapButton.jsx +16 -1
  140. package/src/App/components/MapButton/MapButton.module.scss +12 -1
  141. package/src/App/components/MapButton/MapButton.test.jsx +40 -1
  142. package/src/App/hooks/useContinueEnabledEvaluator.js +31 -0
  143. package/src/App/hooks/useContinueEnabledEvaluator.test.js +118 -0
  144. package/src/App/hooks/useEvaluateProp.js +15 -15
  145. package/src/App/hooks/useEvaluateProp.test.js +80 -76
  146. package/src/App/hooks/useLayoutMeasurements.js +1 -3
  147. package/src/App/hooks/useResizeObserver.js +12 -1
  148. package/src/App/hooks/useResizeObserver.test.js +1 -0
  149. package/src/App/renderer/PluginInits.jsx +2 -0
  150. package/src/App/renderer/mapButtons.js +21 -1
  151. package/src/App/renderer/mapButtons.test.js +46 -1
  152. package/src/App/store/appReducer.js +1 -1
  153. package/src/App/store/appReducer.test.js +37 -0
  154. package/src/InteractiveMap/InteractiveMap.js +10 -0
  155. package/src/InteractiveMap/InteractiveMap.test.js +8 -0
  156. package/src/config/appConfig.js +25 -0
  157. package/src/config/appConfig.test.js +50 -0
  158. package/src/config/defaults.js +1 -0
  159. package/src/scss/settings/_dimensions.scss +1 -0
  160. package/src/services/patternRegistry.js +8 -4
  161. package/src/types.js +7 -0
  162. package/src/utils/patternUtils.js +1 -2
  163. package/src/utils/patternUtils.test.js +9 -0
  164. package/webpack.dev.mjs +10 -0
  165. package/webpack.umd.mjs +1 -1
  166. package/plugins/beta/datasets/dist/adapters/maplibre/esm/im-datasets-ml-adapter.js +0 -1
  167. package/plugins/beta/datasets/dist/adapters/maplibre/esm/index.js +0 -1
  168. package/plugins/beta/datasets/dist/umd/maplibre/im-datasets-maplibre-adapter.js +0 -2
  169. package/plugins/beta/datasets/dist/umd/maplibre/im-datasets-plugin.js +0 -2
  170. package/plugins/beta/datasets/dist/umd/maplibre/index.js +0 -2
  171. package/plugins/beta/datasets/src/adapters/maplibre/index.js +0 -18
  172. package/plugins/beta/datasets/src/adapters/maplibre/layerIds.js +0 -75
  173. package/plugins/beta/datasets/src/adapters/maplibre/patternImages.js +0 -27
  174. package/plugins/beta/datasets/src/adapters/maplibre/symbolImages.js +0 -31
  175. package/plugins/beta/datasets/src/components/KeySvg.jsx +0 -24
  176. package/plugins/beta/datasets/src/components/KeySvgLine.jsx +0 -19
  177. package/plugins/beta/datasets/src/components/KeySvgRect.jsx +0 -22
  178. package/plugins/beta/datasets/src/index.maplibre.umd.js +0 -23
  179. package/plugins/beta/datasets/src/panels/Key.jsx +0 -62
  180. package/plugins/beta/datasets/src/panels/Layers.jsx +0 -141
  181. package/plugins/beta/datasets/src/reducer.js +0 -219
  182. package/plugins/beta/datasets/src/reducers/keyReducer.js +0 -34
  183. package/plugins/beta/datasets/src/utils/filters.js +0 -34
  184. package/plugins/beta/datasets/src/utils/mergeSublayer.js +0 -86
  185. package/plugins/beta/draw-ml/dist/css/index.css +0 -21
  186. /package/plugins/beta/datasets/dist/umd/{maplibre/im-datasets-maplibre-adapter.js.LICENSE.txt → im-datasets-ml-adapter.js.LICENSE.txt} +0 -0
  187. /package/plugins/beta/datasets/dist/umd/{maplibre/im-datasets-plugin.js.LICENSE.txt → im-datasets-plugin.js.LICENSE.txt} +0 -0
  188. /package/plugins/beta/datasets/dist/umd/{maplibre/index.js.LICENSE.txt → index.js.LICENSE.txt} +0 -0
  189. /package/plugins/beta/datasets/src/components/{EmptyKey.jsx → Key/EmptyKey.jsx} +0 -0
  190. /package/plugins/beta/datasets/src/components/{EmptyKey.test.jsx → Key/EmptyKey.test.jsx} +0 -0
  191. /package/plugins/beta/datasets/src/{panels → components/Key}/Key.module.scss +0 -0
  192. /package/plugins/beta/datasets/src/components/{svgProperties.js → Key/svgProperties.js} +0 -0
@@ -0,0 +1,335 @@
1
+ import { Dataset } from './dataset.js'
2
+ import { datasetRegistry } from './datasetRegistry.js'
3
+ import { mappedDatasetsReducer } from '../reducers/mappedDatasetsReducer.js'
4
+ import { datasetsWithGroups } from '../reducers/__data__/demoDatasets.js'
5
+ // Use the mock datasetRegistry with the demo datasets attached before each test.
6
+ // Dataset must be imported before datasetRegistry so that dataset.js is cached before
7
+ // the mock initialization triggers requireActual, avoiding a circular re-entry that
8
+ // would leave datasetRegistry undefined inside the mock.
9
+ jest.mock('./datasetRegistry.js')
10
+
11
+ describe('datasetRegistry', () => {
12
+ beforeEach(() => {
13
+ jest.clearAllMocks()
14
+ datasetRegistry._invalidateCache()
15
+ })
16
+ describe('attach', () => {
17
+ it("doesn't throw when no datasets are attached", () => {
18
+ expect(() => datasetRegistry.attach(undefined)).not.toThrow()
19
+ })
20
+ })
21
+
22
+ describe('getDataset', () => {
23
+ it('returns a Dataset instance for a known id', () => {
24
+ const dataset = datasetRegistry.getDataset('land-covers')
25
+ expect(dataset).toBeInstanceOf(Dataset)
26
+ })
27
+
28
+ it('returns the correct id', () => {
29
+ const dataset = datasetRegistry.getDataset('existing-fields')
30
+ expect(dataset.id).toBe('existing-fields')
31
+ })
32
+
33
+ it('returns a Dataset instance for a sublayer id', () => {
34
+ const dataset = datasetRegistry.getDataset('land-covers-130-131')
35
+ expect(dataset).toBeInstanceOf(Dataset)
36
+ expect(dataset.id).toBe('land-covers-130-131')
37
+ })
38
+
39
+ it('returns undefined for an unknown id', () => {
40
+ expect(datasetRegistry.getDataset('non-existent')).toBeUndefined()
41
+ })
42
+ })
43
+
44
+ describe('forEach', () => {
45
+ it('calls the callback once per dataset in orderedDatasets order', () => {
46
+ const ids = []
47
+ datasetRegistry.forEach(dataset => ids.push(dataset.id))
48
+ expect(ids).toEqual(datasetRegistry._orderedDatasets)
49
+ })
50
+
51
+ it('passes Dataset instances to the callback', () => {
52
+ datasetRegistry.forEach(dataset => expect(dataset).toBeInstanceOf(Dataset))
53
+ })
54
+ })
55
+
56
+ describe('forEachDataset', () => {
57
+ it('calls the callback only for top-level datasets', () => {
58
+ const ids = []
59
+ datasetRegistry.forEachDataset(dataset => ids.push(dataset.id))
60
+ expect(ids).toEqual(['land-covers', 'existing-fields', 'historic-monuments', 'hedge-control'])
61
+ })
62
+
63
+ it('does not include sublayers', () => {
64
+ const ids = []
65
+ datasetRegistry.forEachDataset(dataset => ids.push(dataset.id))
66
+ expect(ids).not.toContain('land-covers-130-131')
67
+ })
68
+
69
+ it('passes Dataset instances to the callback', () => {
70
+ datasetRegistry.forEachDataset(dataset => expect(dataset).toBeInstanceOf(Dataset))
71
+ })
72
+ })
73
+
74
+ describe('topLevelDatasets', () => {
75
+ it('returns only top-level dataset ids', () => {
76
+ const ids = datasetRegistry.topLevelDatasets().map(d => d.id)
77
+ expect(ids).toEqual(['land-covers', 'existing-fields', 'historic-monuments', 'hedge-control'])
78
+ })
79
+
80
+ it('does not include sublayers', () => {
81
+ const ids = datasetRegistry.topLevelDatasets().map(d => d.id)
82
+ expect(ids).not.toContain('land-covers-130-131')
83
+ })
84
+
85
+ it('returns Dataset instances', () => {
86
+ datasetRegistry.topLevelDatasets().forEach(d => expect(d).toBeInstanceOf(Dataset))
87
+ })
88
+ })
89
+
90
+ describe('getPatternAndSymbolConfigs', () => {
91
+ it('returns an object with patternConfigs and symbolConfigs arrays', () => {
92
+ const result = datasetRegistry.getPatternAndSymbolConfigs()
93
+ expect(Array.isArray(result.patternConfigs)).toBe(true)
94
+ expect(Array.isArray(result.symbolConfigs)).toBe(true)
95
+ })
96
+
97
+ it('collects fill pattern configs from sublayers', () => {
98
+ const { patternConfigs } = datasetRegistry.getPatternAndSymbolConfigs()
99
+ // land-covers sublayers each have a fillPattern style
100
+ expect(patternConfigs.length).toBeGreaterThan(0)
101
+ })
102
+
103
+ it('collects symbol configs from sublayers', () => {
104
+ const { symbolConfigs } = datasetRegistry.getPatternAndSymbolConfigs()
105
+ // historic-monuments sublayers inherit symbol: 'square' from their parent
106
+ expect(symbolConfigs.length).toBeGreaterThan(0)
107
+ })
108
+
109
+ it('returns empty arrays when no datasets have patterns or symbols', () => {
110
+ datasetRegistry.attach({ simple: { id: 'simple', style: { stroke: '#ff0000' } } })
111
+ const { patternConfigs, symbolConfigs } = datasetRegistry.getPatternAndSymbolConfigs()
112
+ expect(patternConfigs).toHaveLength(0)
113
+ expect(symbolConfigs).toHaveLength(0)
114
+ })
115
+ })
116
+
117
+ describe('keyItems', () => {
118
+ it('returns an object with an items array and hasGroups boolean', () => {
119
+ const { items, hasGroups } = datasetRegistry.keyItems()
120
+ expect(Array.isArray(items)).toBe(true)
121
+ expect(typeof hasGroups).toBe('boolean')
122
+ })
123
+
124
+ it('includes a sublayers entry for each dataset that has sublayers', () => {
125
+ const { items } = datasetRegistry.keyItems()
126
+ const sublayerItems = items.filter(item => item.type === 'sublayers')
127
+ expect(sublayerItems).toHaveLength(2) // land-covers and historic-monuments
128
+ })
129
+
130
+ it('includes a flat entry for datasets without sublayers and no groupLabel', () => {
131
+ const { items } = datasetRegistry.keyItems()
132
+ const flatItems = items.filter(item => item.type === 'flat')
133
+ expect(flatItems).toHaveLength(1) // existing-fields
134
+ })
135
+
136
+ it('sets hasGroups to true when any item has sublayers', () => {
137
+ const { hasGroups } = datasetRegistry.keyItems()
138
+ expect(hasGroups).toBe(true)
139
+ })
140
+
141
+ it('sets hasGroups to false when all items are flat with no groupLabel', () => {
142
+ datasetRegistry.attach({ simple: { id: 'simple', showInKey: true, visible: true, style: {} } })
143
+ const { hasGroups } = datasetRegistry.keyItems()
144
+ expect(hasGroups).toBe(false)
145
+ })
146
+
147
+ it('sets hasGroups to false when a dataset has only one sublayer and it is not visible', () => {
148
+ const { mappedDatasets: singleHiddenSublayer } = mappedDatasetsReducer({
149
+ datasets: [{
150
+ id: 'single-sublayer-dataset',
151
+ showInKey: true,
152
+ visible: true,
153
+ style: {},
154
+ sublayers: [{ id: 'sub', visible: false, style: {} }]
155
+ }]
156
+ })
157
+ datasetRegistry.attach(singleHiddenSublayer)
158
+ const { hasGroups } = datasetRegistry.keyItems()
159
+ expect(hasGroups).toBe(false)
160
+ })
161
+
162
+ it('only includes visible sublayers in sublayers items', () => {
163
+ const { items } = datasetRegistry.keyItems()
164
+ const landCoversItem = items.find(item => item.type === 'sublayers' && item.dataset.id === 'land-covers')
165
+ const sublayerIds = landCoversItem.sublayers.map(s => s.id)
166
+ expect(sublayerIds).not.toContain('land-covers-379') // land-covers-379 has visible: false
167
+ })
168
+
169
+ it('includes a group entry for datasets sharing a groupLabel', () => {
170
+ const { mappedDatasets: grouped } = mappedDatasetsReducer({ datasets: datasetsWithGroups })
171
+ datasetRegistry.attach(grouped)
172
+ const { items } = datasetRegistry.keyItems()
173
+ const groupItems = items.filter(item => item.type === 'group')
174
+ expect(groupItems).toHaveLength(1)
175
+ expect(groupItems[0].groupLabel).toBe('Test group')
176
+ })
177
+
178
+ it('only adds one group entry per unique groupLabel', () => {
179
+ const { mappedDatasets: grouped } = mappedDatasetsReducer({ datasets: datasetsWithGroups })
180
+ datasetRegistry.attach(grouped)
181
+ const { items } = datasetRegistry.keyItems()
182
+ const groupLabels = items.filter(i => i.type === 'group').map(i => i.groupLabel)
183
+ expect(groupLabels.length).toBe(new Set(groupLabels).size)
184
+ })
185
+
186
+ it('populates group item datasets with matching showInKey datasets', () => {
187
+ const { mappedDatasets: grouped } = mappedDatasetsReducer({ datasets: datasetsWithGroups })
188
+ datasetRegistry.attach(grouped)
189
+ const { items } = datasetRegistry.keyItems()
190
+ const groupItem = items.find(item => item.type === 'group' && item.groupLabel === 'Test group')
191
+ const ids = groupItem.datasets.map(d => d.id)
192
+ expect(ids).toContain('existing-fields')
193
+ expect(ids).toContain('hedge-control')
194
+ })
195
+
196
+ it('caches the result when datasets ref has not changed', () => {
197
+ const result1 = datasetRegistry.keyItems()
198
+ const result2 = datasetRegistry.keyItems()
199
+ expect(result1).toBe(result2)
200
+ })
201
+
202
+ it('recomputes when the datasets ref changes', () => {
203
+ const result1 = datasetRegistry.keyItems()
204
+ datasetRegistry.attach({ simple: { id: 'simple', showInKey: true, visible: true, style: {} } })
205
+ const result2 = datasetRegistry.keyItems()
206
+ expect(result1).not.toBe(result2)
207
+ })
208
+ })
209
+
210
+ describe('attachCreateDataset', () => {
211
+ let originalCreateDataset
212
+
213
+ beforeEach(() => {
214
+ originalCreateDataset = datasetRegistry._createDataset
215
+ })
216
+
217
+ afterEach(() => {
218
+ datasetRegistry._createDataset = originalCreateDataset
219
+ })
220
+
221
+ it('overrides the factory used by getDataset', () => {
222
+ const customFactory = jest.fn((def) => new Dataset(def))
223
+ datasetRegistry.attachCreateDataset(customFactory)
224
+ datasetRegistry.getDataset('land-covers')
225
+ expect(customFactory).toHaveBeenCalled()
226
+ })
227
+
228
+ it('uses the return value of the custom factory', () => {
229
+ const sentinel = { id: 'sentinel' }
230
+ datasetRegistry.attachCreateDataset(() => sentinel)
231
+ const result = datasetRegistry.getDataset('land-covers')
232
+ expect(result).toBe(sentinel)
233
+ })
234
+ })
235
+
236
+ describe('avoid recreating Dataset instances', () => {
237
+ // Because the registryDataset instances are pure and immutable wrappers
238
+ // around the dataset definition, we can return the same registryDataset
239
+ // instances as long as the definitions are the same.
240
+ // Avoiding unnecessary re-calculation of registryDataset instance members
241
+ // like patternConfigs and symbolConfigs which are derived from the definition.
242
+ const createDatasetSpy = jest.spyOn(datasetRegistry, '_createDataset')
243
+
244
+ it("doesn't throw invalidating before caching anything", () => {
245
+ expect(() => {
246
+ datasetRegistry.attach({
247
+ ...datasetRegistry.datasets,
248
+ 'land-covers-130-131': { id: 'land-covers-130-131', style: { stroke: '#00ff00' } }
249
+ })
250
+ }).not.toThrow()
251
+ })
252
+
253
+ it("doesn't throw if a sublayer without a valid parent is attached", () => {
254
+ expect(() => {
255
+ datasetRegistry.attach({
256
+ ...datasetRegistry.datasets,
257
+ 'no-parent': { id: 'no-parent', parentId: 'no-exists', style: {} }
258
+
259
+ })
260
+ datasetRegistry.getDataset('no-parent')
261
+ datasetRegistry.attach({
262
+ ...datasetRegistry.datasets,
263
+ 'no-parent': { id: 'no-parent', parentId: 'no-exists', style: {} }
264
+ })
265
+ }).not.toThrow()
266
+ })
267
+
268
+ it('returns the same Dataset instance for the same dataset definition', () => {
269
+ expect(datasetRegistry.getDataset('land-covers'))
270
+ .toBe(datasetRegistry.getDataset('land-covers'))
271
+ expect(createDatasetSpy).toHaveBeenCalledTimes(1)
272
+ })
273
+
274
+ it('removes cached Dataset instances when new datasets definition are attached', () => {
275
+ const landCovers1 = datasetRegistry.getDataset('land-covers') // createDatasetSpy called once
276
+ const landCovers1Definition = landCovers1._datasetDefinition
277
+ // attach new definitions, with a different land-covers definition
278
+ datasetRegistry.attach({ ...datasetRegistry.datasets, 'land-covers': { id: 'land-covers', style: {} } })
279
+
280
+ expect(datasetRegistry.getDataset('land-covers')).not.toBe(landCovers1) // should be different as the definition has changed
281
+ expect(createDatasetSpy).toHaveBeenCalledTimes(2)
282
+ // ensure the old definition is no longer cached - which could cause a memory leak
283
+ expect(datasetRegistry._definitionCache.getByDefinition(landCovers1Definition)).toBeUndefined()
284
+ })
285
+
286
+ it('should invalidate the cache of sublayers(children) when a parent dataset definition is updated', () => {
287
+ const landCovers = datasetRegistry.getDataset('land-covers')
288
+ const landCoversSublayer = datasetRegistry.getDataset('land-covers-130-131')
289
+ expect(createDatasetSpy).toHaveBeenCalledTimes(2)
290
+ const landCoversDefinition = landCovers._datasetDefinition
291
+
292
+ // Update the parent dataset definition, which should invalidate both the parent and sublayer cache entries
293
+ const newLandCoversDefinition = { ...landCoversDefinition, style: { stroke: '#00ff00' } }
294
+ datasetRegistry.attach({ ...datasetRegistry.datasets, 'land-covers': newLandCoversDefinition })
295
+
296
+ expect(datasetRegistry.getDataset('land-covers')).not.toBe(landCovers)
297
+ expect(datasetRegistry.getDataset('land-covers-130-131')).not.toBe(landCoversSublayer)
298
+ expect(createDatasetSpy).toHaveBeenCalledTimes(4)
299
+ })
300
+
301
+ it('should invalidate the cache of the parent and siblings when a sublayers dataset definition is updated', () => {
302
+ const landCovers = datasetRegistry.getDataset('land-covers')
303
+ const landCoversSublayer130 = datasetRegistry.getDataset('land-covers-130-131')
304
+ const landCoversSublayer332 = datasetRegistry.getDataset('land-covers-332')
305
+ expect(createDatasetSpy).toHaveBeenCalledTimes(3)
306
+ const sublayerDefinition = landCoversSublayer130._datasetDefinition
307
+
308
+ // Update the parent dataset definition, which should invalidate both the parent and sublayer cache entries
309
+ const newSublayerDefinition = { ...sublayerDefinition, style: { stroke: '#00ff00' } }
310
+ datasetRegistry.attach({ ...datasetRegistry.datasets, 'land-covers-130-131': newSublayerDefinition })
311
+
312
+ expect(datasetRegistry.getDataset('land-covers')).not.toBe(landCovers)
313
+ expect(datasetRegistry.getDataset('land-covers-130-131')).not.toBe(landCoversSublayer130)
314
+ expect(datasetRegistry.getDataset('land-covers-332')).not.toBe(landCoversSublayer332)
315
+
316
+ expect(createDatasetSpy).toHaveBeenCalledTimes(6)
317
+ })
318
+
319
+ it('should not invalidate the cache of different datasets when a definition is updated', () => {
320
+ const landCovers = datasetRegistry.getDataset('land-covers')
321
+ const existingFields = datasetRegistry.getDataset('existing-fields')
322
+ expect(createDatasetSpy).toHaveBeenCalledTimes(2)
323
+ const existingFieldsDefinition = existingFields._datasetDefinition
324
+
325
+ // Update existing-fields, which should not invalidate land-covers
326
+ const newExistingFieldsDefinition = { id: 'existing-fields', style: {} }
327
+ datasetRegistry.attach({ ...datasetRegistry.datasets, 'existing-fields': newExistingFieldsDefinition })
328
+
329
+ expect(datasetRegistry.getDataset('land-covers')).toBe(landCovers)
330
+ expect(datasetRegistry.getDataset('existing-fields')).not.toBe(existingFields)
331
+ expect(createDatasetSpy).toHaveBeenCalledTimes(3) // should have been called one more time
332
+ expect(datasetRegistry._definitionCache.getByDefinition(existingFieldsDefinition)).toBeUndefined() // old definition should no longer be cached
333
+ })
334
+ })
335
+ })
@@ -0,0 +1,17 @@
1
+ export class DynamicGeoJson {
2
+ constructor (dynamicGeoJSONDefinition) {
3
+ this.id = dynamicGeoJSONDefinition.id
4
+ this.url = dynamicGeoJSONDefinition.url
5
+ this.transformRequest = dynamicGeoJSONDefinition.transformRequest
6
+ this.maxFeatures = dynamicGeoJSONDefinition.maxFeatures
7
+ this.minZoom = dynamicGeoJSONDefinition.minZoom
8
+ this.idProperty = dynamicGeoJSONDefinition.idProperty
9
+ this.hiddenFeaturesIdExpression = ['to-string', ['get', this.idProperty]]
10
+ this.sourceId = `geojson-dynamic-${this.id}`
11
+ this.source = {
12
+ type: 'geojson',
13
+ data: { type: 'FeatureCollection', features: [] },
14
+ generateId: true
15
+ }
16
+ }
17
+ }
@@ -0,0 +1,36 @@
1
+ let globalState = {}
2
+
3
+ export const attachGlobalState = (globalStateRef) => {
4
+ globalState = globalStateRef
5
+ }
6
+
7
+ // TODO - remove this once testing finishes
8
+ window.getGlobalState = () => Object.freeze(globalState)
9
+
10
+ const calculateLocalOpacity = (opacityValue, parentOpacityValue) => {
11
+ if (opacityValue === undefined) {
12
+ if (parentOpacityValue === undefined) {
13
+ return globalState.opacity
14
+ }
15
+ return parentOpacityValue
16
+ }
17
+ return opacityValue
18
+ }
19
+
20
+ const multiplyOpacities = (opacityValue = 1, parentOpacityValue = 1) => {
21
+ return Math.round((opacityValue * parentOpacityValue * globalState.opacity + Number.EPSILON) * 100) / 100
22
+ }
23
+
24
+ export const calculateOpacity = (opacityValue, parentOpacityValue) => {
25
+ switch (globalState.opacityMode) {
26
+ case 'global':
27
+ return globalState.opacity
28
+ case 'multiply':
29
+ return multiplyOpacities(opacityValue, parentOpacityValue)
30
+ case 'dataset':
31
+ default:
32
+ return calculateLocalOpacity(opacityValue, parentOpacityValue)
33
+ }
34
+ }
35
+
36
+ export const getGlobalVisibility = () => globalState.visible