@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.
- package/README.md +2 -2
- package/assets/images/bright-ofm-thumb.jpg +0 -0
- package/assets/images/dark-ofm-thumb.jpg +0 -0
- package/assets/images/road-ofm-thumb.jpg +0 -0
- package/assets/templates/add-polygons.njk +143 -0
- package/assets/templates/add-symbols.njk +119 -0
- package/assets/templates/{map.njk → basic-map.njk} +9 -17
- package/assets/templates/button-map.njk +56 -0
- package/assets/templates/draw-tools.njk +161 -0
- package/assets/templates/marker-panel.njk +91 -0
- package/assets/templates/place-marker.njk +104 -0
- package/assets/templates/search-control.njk +98 -0
- package/assets/templates/select-feature.njk +101 -0
- package/assets/templates/style-switcher.njk +87 -0
- package/assets/templates/toggle-marker-label.njk +74 -0
- package/dist/css/index.css +1 -1
- package/dist/esm/im-core.js +1 -1
- package/dist/esm/im-shell.js +1 -1
- package/dist/umd/im-core.js +1 -1
- package/dist/umd/index.js +1 -1
- package/docs/api/context.md +4 -4
- package/docs/api.md +166 -37
- package/docs/assets/css/docusaurus.scss +36 -4
- package/docs/assets/images/add-polygons.jpg +0 -0
- package/docs/assets/images/add-symbols.jpg +0 -0
- package/docs/assets/images/basic-map.jpg +0 -0
- package/docs/assets/images/button-first.jpg +0 -0
- package/docs/assets/images/deuteranopia-ozs-map-thumb.jpg +0 -0
- package/docs/assets/images/draw.jpg +0 -0
- package/docs/assets/images/marker-panel.jpg +0 -0
- package/docs/assets/images/night-ozs-map-thumb.jpg +0 -0
- package/docs/assets/images/outdoor-ozs-map-thumb.jpg +0 -0
- package/docs/assets/images/place-marker.jpg +0 -0
- package/docs/assets/images/screenshot.jpg +0 -0
- package/docs/assets/images/search.jpg +0 -0
- package/docs/assets/images/select-feature.jpg +0 -0
- package/docs/assets/images/style-switcher.jpg +0 -0
- package/docs/assets/images/toggle-marker-label.jpg +0 -0
- package/docs/assets/images/tritanopia-ozs-map-thumb.jpg +0 -0
- package/docs/examples/add-marker-with-panel.mdx +3 -3
- package/docs/examples/add-polygons.mdx +225 -0
- package/docs/examples/add-symbols.mdx +177 -0
- package/docs/examples/basic-map.mdx +2 -2
- package/docs/examples/draw-tools.mdx +271 -0
- package/docs/examples/index.mdx +95 -11
- package/docs/examples/place-marker.mdx +151 -0
- package/docs/examples/search-control.mdx +71 -0
- package/docs/examples/select-feature.mdx +146 -0
- package/docs/examples/style-switcher.mdx +135 -0
- package/docs/examples/toggle-marker-label.mdx +3 -3
- package/docs/plugins/datasets.md +157 -122
- package/docs/plugins/interact.md +1 -4
- package/docusaurus.config.cjs +9 -2
- package/govuk-prototype-kit.config.json +52 -2
- package/package.json +1 -1
- package/plugins/beta/datasets/dist/css/index.css +5 -2
- package/plugins/beta/datasets/dist/esm/im-datasets-ml-adapter.js +1 -0
- package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +1 -1
- package/plugins/beta/datasets/dist/esm/index.js +1 -1
- package/plugins/beta/datasets/dist/umd/im-datasets-ml-adapter.js +2 -0
- package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +2 -0
- package/plugins/beta/datasets/dist/umd/index.js +2 -0
- package/plugins/beta/datasets/src/adapters/loadLayerAdapter.js +33 -0
- package/plugins/beta/datasets/src/adapters/maplibre/layerBuilders.js +44 -120
- package/plugins/beta/datasets/src/adapters/maplibre/maplibreLayerAdapter.js +145 -245
- package/plugins/beta/datasets/src/adapters/maplibre/maplibreLayerAdapter.test.js +516 -0
- package/plugins/beta/datasets/src/adapters/maplibre/registry/mapLibreDataset.js +209 -0
- package/plugins/beta/datasets/src/adapters/maplibre/registry/mapLibreDataset.test.js +483 -0
- package/plugins/beta/datasets/src/api/addDataset.js +2 -5
- package/plugins/beta/datasets/src/api/addDataset.test.js +16 -0
- package/plugins/beta/datasets/src/api/getOpacity.js +14 -14
- package/plugins/beta/datasets/src/api/getOpacity.test.js +72 -0
- package/plugins/beta/datasets/src/api/getStyle.js +8 -9
- package/plugins/beta/datasets/src/api/getStyle.test.js +42 -0
- package/plugins/beta/datasets/src/api/removeDataset.js +7 -7
- package/plugins/beta/datasets/src/api/removeDataset.test.js +45 -0
- package/plugins/beta/datasets/src/api/setData.js +9 -3
- package/plugins/beta/datasets/src/api/setData.test.js +62 -0
- package/plugins/beta/datasets/src/api/setDatasetVisibility.js +5 -30
- package/plugins/beta/datasets/src/api/setDatasetVisibility.test.js +31 -0
- package/plugins/beta/datasets/src/api/setFeatureVisibility.js +9 -18
- package/plugins/beta/datasets/src/api/setFeatureVisibility.test.js +74 -0
- package/plugins/beta/datasets/src/api/setGlobals.js +20 -0
- package/plugins/beta/datasets/src/api/setGlobals.test.js +63 -0
- package/plugins/beta/datasets/src/api/setOpacity.js +8 -27
- package/plugins/beta/datasets/src/api/setOpacity.test.js +31 -0
- package/plugins/beta/datasets/src/api/setStyle.js +5 -21
- package/plugins/beta/datasets/src/api/setStyle.test.js +53 -0
- package/plugins/beta/datasets/src/components/Key/Key.jsx +51 -0
- package/plugins/beta/datasets/src/components/Key/KeyGroupItem.jsx +18 -0
- package/plugins/beta/datasets/src/components/Key/KeyItem.jsx +20 -0
- package/plugins/beta/datasets/src/components/Key/KeySvg.jsx +22 -0
- package/plugins/beta/datasets/src/components/Key/KeySvg.test.jsx +87 -0
- package/plugins/beta/datasets/src/components/Key/KeySvgLine.jsx +19 -0
- package/plugins/beta/datasets/src/components/Key/KeySvgLine.test.jsx +71 -0
- package/plugins/beta/datasets/src/components/{KeySvgPattern.jsx → Key/KeySvgPattern.jsx} +3 -4
- package/plugins/beta/datasets/src/components/Key/KeySvgPattern.test.jsx +55 -0
- package/plugins/beta/datasets/src/components/Key/KeySvgRect.jsx +22 -0
- package/plugins/beta/datasets/src/components/Key/KeySvgRect.test.jsx +85 -0
- package/plugins/beta/datasets/src/components/{KeySvgSymbol.jsx → Key/KeySvgSymbol.jsx} +5 -5
- package/plugins/beta/datasets/src/components/Key/KeySvgSymbol.test.jsx +80 -0
- package/plugins/beta/datasets/src/components/Key/svgProperties.test.js +61 -0
- package/plugins/beta/datasets/src/{panels → components/LayersMenu}/Layers.module.scss +15 -11
- package/plugins/beta/datasets/src/components/LayersMenu/LayersMenu.jsx +33 -0
- package/plugins/beta/datasets/src/components/LayersMenu/LayersMenuCheckbox.jsx +31 -0
- package/plugins/beta/datasets/src/components/LayersMenu/LayersMenuGroupWrapper.jsx +19 -0
- package/plugins/beta/datasets/src/datasets.scss +2 -2
- package/plugins/beta/datasets/src/fetch/createDynamicSource.js +8 -8
- package/plugins/beta/datasets/src/{DatasetsInit.jsx → initialise/DatasetsInit.jsx} +19 -21
- package/plugins/beta/datasets/src/{defaults.js → initialise/defaults.js} +17 -2
- package/plugins/beta/datasets/src/initialise/defaults.test.js +127 -0
- package/plugins/beta/datasets/src/{datasets.js → initialise/initialiseDatasets.js} +30 -26
- package/plugins/beta/datasets/src/initialise/initialiseDatasets.test.js +273 -0
- package/plugins/beta/datasets/src/manifest.js +8 -6
- package/plugins/beta/datasets/src/reducers/__data__/demoDatasets.js +253 -0
- package/plugins/beta/datasets/src/reducers/datasetsToMenu.js +83 -0
- package/plugins/beta/datasets/src/reducers/datasetsToMenu.test.js +84 -0
- package/plugins/beta/datasets/src/reducers/mappedDatasetsReducer.js +41 -0
- package/plugins/beta/datasets/src/reducers/mappedDatasetsReducer.test.js +117 -0
- package/plugins/beta/datasets/src/reducers/pluginState.js +206 -0
- package/plugins/beta/datasets/src/reducers/pluginState.test.js +386 -0
- package/plugins/beta/datasets/src/registry/__mocks__/datasetRegistry.js +29 -0
- package/plugins/beta/datasets/src/registry/createDataset.js +5 -0
- package/plugins/beta/datasets/src/registry/dataset.js +176 -0
- package/plugins/beta/datasets/src/registry/dataset.test.js +497 -0
- package/plugins/beta/datasets/src/registry/datasetDefinitionCache.js +35 -0
- package/plugins/beta/datasets/src/registry/datasetRegistry.js +116 -0
- package/plugins/beta/datasets/src/registry/datasetRegistry.test.js +335 -0
- package/plugins/beta/datasets/src/registry/dynamicGeoJson.js +17 -0
- package/plugins/beta/datasets/src/registry/globalDataset.js +36 -0
- package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
- package/plugins/beta/draw-es/src/api/addFeature.js +12 -3
- package/plugins/beta/draw-es/src/events.js +8 -3
- package/plugins/beta/draw-es/src/events.test.js +2 -0
- package/plugins/beta/draw-es/src/graphic.js +26 -2
- package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
- package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
- package/plugins/beta/draw-ml/src/DrawInit.jsx +8 -0
- package/plugins/beta/draw-ml/src/api/newLine.js +1 -0
- package/plugins/beta/draw-ml/src/api/newPolygon.js +1 -0
- package/plugins/beta/draw-ml/src/events.js +13 -1
- package/plugins/beta/draw-ml/src/index.js +1 -1
- package/plugins/beta/draw-ml/src/manifest.js +3 -0
- package/plugins/beta/draw-ml/src/modes/createDrawMode.js +21 -12
- package/plugins/beta/draw-ml/src/modes/editVertexMode.js +4 -0
- package/plugins/beta/draw-ol/dist/esm/EditMode.js +1 -1
- package/plugins/beta/draw-ol/dist/esm/im-draw-ol-plugin.js +1 -1
- package/plugins/beta/draw-ol/src/edit/keyboardHandler.js +4 -2
- package/plugins/beta/map-styles/src/mapStyles.scss +41 -41
- package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
- package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
- package/plugins/interact/dist/umd/index.js +1 -1
- package/plugins/interact/src/InteractInit.jsx +3 -5
- package/plugins/interact/src/InteractInit.test.js +1 -3
- package/plugins/interact/src/events.js +2 -29
- package/plugins/interact/src/events.test.js +4 -102
- package/plugins/interact/src/hooks/useAttachEvents.js +3 -4
- package/plugins/interact/src/hooks/useAttachEvents.test.js +1 -3
- package/plugins/interact/src/hooks/useCrossHairVisibility.js +4 -10
- package/plugins/interact/src/hooks/useCrossHairVisibility.test.js +5 -10
- package/plugins/interact/src/hooks/useHighlightSync.js +1 -10
- package/plugins/interact/src/hooks/useHighlightSync.test.js +2 -21
- package/plugins/interact/src/hooks/useInteractionHandlers.js +15 -12
- package/plugins/interact/src/hooks/useInteractionHandlers.test.js +28 -29
- package/plugins/interact/src/manifest.js +0 -35
- package/plugins/interact/src/manifest.test.js +3 -43
- package/plugins/interact/src/reducer.js +4 -22
- package/plugins/interact/src/reducer.test.js +11 -39
- package/plugins/search/dist/css/index.css +5 -2
- package/plugins/search/src/components/Form/Form.module.scss +5 -2
- package/plugins/search/src/search.scss +4 -4
- package/providers/beta/esri/dist/esm/im-esri-provider.js +1 -1
- package/providers/beta/esri/src/esriProvider.js +4 -0
- package/providers/beta/openlayers/dist/esm/im-openlayers-provider.js +1 -1
- package/providers/beta/openlayers/dist/umd/im-openlayers-provider.js +1 -1
- package/providers/beta/openlayers/src/openlayersProvider.js +4 -0
- package/providers/beta/openlayers/src/openlayersProvider.test.js +5 -0
- package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
- package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
- package/providers/maplibre/src/maplibreProvider.js +6 -4
- package/providers/maplibre/src/maplibreProvider.test.js +5 -0
- package/providers/maplibre/src/utils/highlightFeatures.js +74 -25
- package/providers/maplibre/src/utils/highlightFeatures.test.js +132 -0
- package/providers/maplibre/src/utils/patternImages.js +2 -4
- package/providers/maplibre/src/utils/symbolImages.js +1 -4
- package/rollup.esm.mjs +2 -8
- package/src/App/components/CrossHair/CrossHair.module.scss +8 -8
- package/src/App/components/MapButton/MapButton.jsx +16 -1
- package/src/App/components/MapButton/MapButton.module.scss +12 -1
- package/src/App/components/MapButton/MapButton.test.jsx +40 -1
- package/src/App/hooks/useContinueEnabledEvaluator.js +31 -0
- package/src/App/hooks/useContinueEnabledEvaluator.test.js +118 -0
- package/src/App/hooks/useEvaluateProp.js +15 -15
- package/src/App/hooks/useEvaluateProp.test.js +80 -76
- package/src/App/hooks/useLayoutMeasurements.js +11 -5
- package/src/App/hooks/useResizeObserver.js +15 -2
- package/src/App/hooks/useResizeObserver.test.js +1 -0
- package/src/App/renderer/PluginInits.jsx +2 -0
- package/src/App/renderer/mapButtons.js +21 -1
- package/src/App/renderer/mapButtons.test.js +46 -1
- package/src/App/store/appReducer.js +1 -1
- package/src/App/store/appReducer.test.js +37 -0
- package/src/InteractiveMap/InteractiveMap.js +14 -4
- package/src/InteractiveMap/InteractiveMap.test.js +11 -3
- package/src/InteractiveMap/behaviourController.js +3 -1
- package/src/InteractiveMap/behaviourController.test.js +6 -3
- package/src/InteractiveMap/domStateManager.js +2 -0
- package/src/config/appConfig.js +25 -0
- package/src/config/appConfig.test.js +50 -0
- package/src/config/defaults.js +1 -0
- package/src/config/events.js +21 -3
- package/src/scss/settings/_dimensions.scss +1 -0
- package/src/services/patternRegistry.js +8 -4
- package/src/types.js +7 -0
- package/src/utils/patternUtils.js +1 -2
- package/src/utils/patternUtils.test.js +9 -0
- package/webpack.dev.mjs +10 -0
- package/webpack.umd.mjs +1 -1
- package/docs/assets/images/screens-blue.jpg +0 -0
- package/docs/assets/images/screens-white.jpg +0 -0
- package/plugins/beta/datasets/dist/adapters/maplibre/esm/im-datasets-ml-adapter.js +0 -1
- package/plugins/beta/datasets/dist/adapters/maplibre/esm/index.js +0 -1
- package/plugins/beta/datasets/dist/umd/maplibre/im-datasets-maplibre-adapter.js +0 -2
- package/plugins/beta/datasets/dist/umd/maplibre/im-datasets-plugin.js +0 -2
- package/plugins/beta/datasets/dist/umd/maplibre/index.js +0 -2
- package/plugins/beta/datasets/src/adapters/maplibre/index.js +0 -18
- package/plugins/beta/datasets/src/adapters/maplibre/layerIds.js +0 -75
- package/plugins/beta/datasets/src/adapters/maplibre/patternImages.js +0 -27
- package/plugins/beta/datasets/src/adapters/maplibre/symbolImages.js +0 -31
- package/plugins/beta/datasets/src/components/KeySvg.jsx +0 -24
- package/plugins/beta/datasets/src/components/KeySvgLine.jsx +0 -19
- package/plugins/beta/datasets/src/components/KeySvgRect.jsx +0 -22
- package/plugins/beta/datasets/src/index.maplibre.umd.js +0 -23
- package/plugins/beta/datasets/src/panels/Key.jsx +0 -62
- package/plugins/beta/datasets/src/panels/Layers.jsx +0 -141
- package/plugins/beta/datasets/src/reducer.js +0 -219
- package/plugins/beta/datasets/src/reducers/keyReducer.js +0 -34
- package/plugins/beta/datasets/src/utils/filters.js +0 -34
- package/plugins/beta/datasets/src/utils/mergeSublayer.js +0 -86
- package/plugins/beta/draw-ml/dist/css/index.css +0 -21
- /package/plugins/beta/datasets/dist/umd/{maplibre/im-datasets-maplibre-adapter.js.LICENSE.txt → im-datasets-ml-adapter.js.LICENSE.txt} +0 -0
- /package/plugins/beta/datasets/dist/umd/{maplibre/im-datasets-plugin.js.LICENSE.txt → im-datasets-plugin.js.LICENSE.txt} +0 -0
- /package/plugins/beta/datasets/dist/umd/{maplibre/index.js.LICENSE.txt → index.js.LICENSE.txt} +0 -0
- /package/plugins/beta/datasets/src/components/{EmptyKey.jsx → Key/EmptyKey.jsx} +0 -0
- /package/plugins/beta/datasets/src/components/{EmptyKey.test.jsx → Key/EmptyKey.test.jsx} +0 -0
- /package/plugins/beta/datasets/src/{panels → components/Key}/Key.module.scss +0 -0
- /package/plugins/beta/datasets/src/components/{svgProperties.js → Key/svgProperties.js} +0 -0
|
@@ -23,25 +23,25 @@ export function useEvaluateProp () {
|
|
|
23
23
|
iconRegistry: getIconRegistry()
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
function
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
...pluginEntry.config
|
|
36
|
-
}
|
|
37
|
-
: {}
|
|
38
|
-
// Only include this plugin's state + dispatch
|
|
26
|
+
function buildPluginContext (pluginId) {
|
|
27
|
+
if (!pluginId) { return { pluginStates: pluginContext?.state ?? {} } }
|
|
28
|
+
|
|
29
|
+
const pluginEntry = appConfig.pluginRegistry.registeredPlugins.find(p => p.id === pluginId)
|
|
30
|
+
const pluginConfig = pluginEntry ? { pluginId: pluginEntry.id, ...pluginEntry.config } : {}
|
|
31
|
+
|
|
32
|
+
// Only include isolated plugin state when the plugin has registered a reducer.
|
|
33
|
+
// Framework entries like 'appConfig' have no reducer so their buttons get pluginStates instead.
|
|
34
|
+
if (Object.hasOwn(pluginContext?.state ?? {}, pluginId)) {
|
|
39
35
|
const stateForPlugin = pluginContext?.state?.[pluginId] ?? {}
|
|
40
|
-
pluginState = { ...stateForPlugin, dispatch: pluginContext?.dispatch }
|
|
36
|
+
const pluginState = { ...stateForPlugin, dispatch: pluginContext?.dispatch }
|
|
37
|
+
return { pluginConfig, pluginState }
|
|
41
38
|
}
|
|
42
39
|
|
|
43
|
-
|
|
40
|
+
return { pluginConfig, pluginStates: pluginContext?.state ?? {} }
|
|
41
|
+
}
|
|
44
42
|
|
|
43
|
+
function evaluateProp (prop, pluginId) {
|
|
44
|
+
const fullContext = { ...ctx, ...buildPluginContext(pluginId) }
|
|
45
45
|
return typeof prop === 'function' ? prop(fullContext) : prop
|
|
46
46
|
}
|
|
47
47
|
|
|
@@ -8,57 +8,50 @@ import * as serviceStore from '../store/serviceContext.js'
|
|
|
8
8
|
import { PluginContext } from '../store/PluginProvider.jsx'
|
|
9
9
|
import * as iconRegistryModule from '../registry/iconRegistry.js'
|
|
10
10
|
|
|
11
|
-
// --- Mock the dependencies ---
|
|
12
11
|
jest.mock('../store/configContext.js', () => ({ useConfig: jest.fn() }))
|
|
13
12
|
jest.mock('../store/appContext.js', () => ({ useApp: jest.fn() }))
|
|
14
13
|
jest.mock('../store/mapContext.js', () => ({ useMap: jest.fn() }))
|
|
15
14
|
jest.mock('../store/serviceContext.js', () => ({ useService: jest.fn() }))
|
|
16
15
|
jest.mock('../registry/iconRegistry.js', () => ({ getIconRegistry: jest.fn() }))
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
})
|
|
17
|
+
const RAW_VALUE = 'raw-test-value'
|
|
18
|
+
const pluginDispatch = jest.fn()
|
|
19
|
+
const pluginContextValue = {
|
|
20
|
+
state: { myPlugin: { foo: 'bar' } },
|
|
21
|
+
dispatch: pluginDispatch
|
|
22
|
+
}
|
|
23
|
+
const mockPluginRegistry = {
|
|
24
|
+
registeredPlugins: [],
|
|
25
|
+
registerPlugin: jest.fn(),
|
|
26
|
+
clear: jest.fn()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const withPluginContext = ({ children }) => (
|
|
30
|
+
<PluginContext.Provider value={pluginContextValue}>
|
|
31
|
+
{children}
|
|
32
|
+
</PluginContext.Provider>
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
jest.clearAllMocks()
|
|
37
|
+
mockPluginRegistry.registeredPlugins = []
|
|
38
|
+
configStore.useConfig.mockReturnValue({ mapProvider: { name: 'leaflet' }, pluginRegistry: mockPluginRegistry })
|
|
39
|
+
appStore.useApp.mockReturnValue({ user: 'alice' })
|
|
40
|
+
mapStore.useMap.mockReturnValue({ zoom: 5 })
|
|
41
|
+
serviceStore.useService.mockReturnValue({ reverseGeocode: jest.fn() })
|
|
42
|
+
iconRegistryModule.getIconRegistry.mockReturnValue({ close: '<svg/>' })
|
|
43
|
+
})
|
|
43
44
|
|
|
45
|
+
describe('useEvaluateProp — basic evaluation', () => {
|
|
44
46
|
it('returns the raw prop if it is not a function', () => {
|
|
45
47
|
const { result } = renderHook(() => useEvaluateProp())
|
|
46
|
-
|
|
47
|
-
const value = evaluate(42)
|
|
48
|
-
expect(value).toBe(42)
|
|
48
|
+
expect(result.current(RAW_VALUE)).toBe(RAW_VALUE)
|
|
49
49
|
})
|
|
50
50
|
|
|
51
51
|
it('calls prop function with full context when no pluginId provided', () => {
|
|
52
52
|
const { result } = renderHook(() => useEvaluateProp())
|
|
53
|
-
const evaluate = result.current
|
|
54
|
-
|
|
55
53
|
const fn = jest.fn(ctx => ctx.appState.user)
|
|
56
|
-
|
|
57
|
-
const value = evaluate(fn)
|
|
58
|
-
|
|
59
|
-
expect(fn).toHaveBeenCalled()
|
|
60
|
-
expect(value).toBe('alice')
|
|
61
|
-
|
|
54
|
+
expect(result.current(fn)).toBe('alice')
|
|
62
55
|
expect(fn.mock.calls[0][0]).toMatchObject({
|
|
63
56
|
appConfig: { mapProvider: { name: 'leaflet' } },
|
|
64
57
|
appState: { user: 'alice' },
|
|
@@ -69,56 +62,67 @@ describe('useEvaluateProp hook', () => {
|
|
|
69
62
|
})
|
|
70
63
|
})
|
|
71
64
|
|
|
72
|
-
it('
|
|
73
|
-
|
|
65
|
+
it('exposes ctx property on the returned evaluateProp function', () => {
|
|
66
|
+
const { result } = renderHook(() => useEvaluateProp())
|
|
67
|
+
expect(result.current.ctx).toMatchObject({
|
|
68
|
+
appConfig: { mapProvider: { name: 'leaflet' } },
|
|
69
|
+
appState: { user: 'alice' },
|
|
70
|
+
mapState: { zoom: 5 },
|
|
71
|
+
services: { reverseGeocode: expect.any(Function) },
|
|
72
|
+
mapProvider: { name: 'leaflet' },
|
|
73
|
+
iconRegistry: { close: '<svg/>' }
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
})
|
|
74
77
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
describe('useEvaluateProp — pluginStates (core/framework buttons)', () => {
|
|
79
|
+
it('includes pluginStates in context when no pluginId provided', () => {
|
|
80
|
+
const { result } = renderHook(() => useEvaluateProp(), { wrapper: withPluginContext })
|
|
81
|
+
expect(result.current(ctx => ctx.pluginStates)).toEqual({ myPlugin: { foo: 'bar' } })
|
|
82
|
+
})
|
|
80
83
|
|
|
81
|
-
|
|
82
|
-
|
|
84
|
+
it('includes pluginStates for appConfig plugin (no reducer, not in state)', () => {
|
|
85
|
+
mockPluginRegistry.registeredPlugins.push({ id: 'appConfig', config: {} })
|
|
86
|
+
const { result } = renderHook(() => useEvaluateProp(), { wrapper: withPluginContext })
|
|
87
|
+
expect(result.current(ctx => ctx.pluginStates, 'appConfig')).toEqual({ myPlugin: { foo: 'bar' } })
|
|
88
|
+
})
|
|
83
89
|
|
|
84
|
-
|
|
85
|
-
|
|
90
|
+
it('falls back to empty pluginStates when PluginContext is not provided', () => {
|
|
91
|
+
mockPluginRegistry.registeredPlugins.push({ id: 'appConfig', config: {} })
|
|
92
|
+
const { result } = renderHook(() => useEvaluateProp())
|
|
93
|
+
expect(result.current(ctx => ctx.pluginStates, 'appConfig')).toEqual({})
|
|
94
|
+
})
|
|
95
|
+
})
|
|
86
96
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
97
|
+
describe('useEvaluateProp — plugin state isolation', () => {
|
|
98
|
+
it('includes pluginConfig and pluginState for real plugin buttons', () => {
|
|
99
|
+
mockPluginRegistry.registeredPlugins.push({ id: 'myPlugin', config: { includeModes: ['edit'], excludeModes: ['view'] } })
|
|
100
|
+
const { result } = renderHook(() => useEvaluateProp(), { wrapper: withPluginContext })
|
|
101
|
+
const ctx = result.current(c => c, 'myPlugin')
|
|
102
|
+
expect(ctx.pluginConfig).toEqual({ pluginId: 'myPlugin', includeModes: ['edit'], excludeModes: ['view'] })
|
|
103
|
+
expect(ctx.pluginState).toMatchObject({ foo: 'bar', dispatch: pluginDispatch })
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('does not include pluginStates for real plugin buttons', () => {
|
|
107
|
+
mockPluginRegistry.registeredPlugins.push({ id: 'myPlugin', config: {} })
|
|
108
|
+
const { result } = renderHook(() => useEvaluateProp(), { wrapper: withPluginContext })
|
|
109
|
+
expect(result.current(ctx => ctx.pluginStates, 'myPlugin')).toBeUndefined()
|
|
96
110
|
})
|
|
97
111
|
|
|
98
112
|
it('returns empty pluginConfig if plugin not registered', () => {
|
|
113
|
+
const { result } = renderHook(() => useEvaluateProp(), { wrapper: withPluginContext })
|
|
114
|
+
expect(result.current(ctx => ctx.pluginConfig, 'unknownPlugin')).toEqual({})
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('falls back to empty object when plugin state entry is null', () => {
|
|
118
|
+
mockPluginRegistry.registeredPlugins.push({ id: 'myPlugin', config: {} })
|
|
99
119
|
const wrapper = ({ children }) => (
|
|
100
|
-
<PluginContext.Provider value={
|
|
120
|
+
<PluginContext.Provider value={{ state: { myPlugin: null }, dispatch: pluginDispatch }}>
|
|
101
121
|
{children}
|
|
102
122
|
</PluginContext.Provider>
|
|
103
123
|
)
|
|
104
124
|
const { result } = renderHook(() => useEvaluateProp(), { wrapper })
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
const fn = jest.fn(ctx => ctx.pluginConfig)
|
|
108
|
-
const value = evaluate(fn, 'unknownPlugin')
|
|
109
|
-
expect(value).toEqual({})
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
it('exposes ctx property on the returned evaluateProp function', () => {
|
|
113
|
-
const { result } = renderHook(() => useEvaluateProp())
|
|
114
|
-
const evaluate = result.current
|
|
115
|
-
expect(evaluate.ctx).toMatchObject({
|
|
116
|
-
appConfig: { mapProvider: { name: 'leaflet' } },
|
|
117
|
-
appState: { user: 'alice' },
|
|
118
|
-
mapState: { zoom: 5 },
|
|
119
|
-
services: { reverseGeocode: expect.any(Function) },
|
|
120
|
-
mapProvider: { name: 'leaflet' },
|
|
121
|
-
iconRegistry: { close: '<svg/>' }
|
|
122
|
-
})
|
|
125
|
+
const ctx = result.current(c => c, 'myPlugin')
|
|
126
|
+
expect(ctx.pluginState).toMatchObject({ dispatch: pluginDispatch })
|
|
123
127
|
})
|
|
124
128
|
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useLayoutEffect } from 'react'
|
|
1
|
+
import { useLayoutEffect, useMemo } from 'react'
|
|
2
2
|
import { useResizeObserver } from './useResizeObserver.js'
|
|
3
3
|
import { useApp } from '../store/appContext.js'
|
|
4
4
|
import { useMap } from '../store/mapContext.js'
|
|
@@ -153,9 +153,15 @@ export function useLayoutMeasurements () {
|
|
|
153
153
|
// actions buttons, etc.). Safe zone is intentionally not dispatched here —
|
|
154
154
|
// that is Effect 2's responsibility.
|
|
155
155
|
// --------------------------------
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
156
|
+
// Stable reference — all entries are ref objects from useApp that never change.
|
|
157
|
+
// Prevents useResizeObserver's effect from re-running (and cancelling its RAF)
|
|
158
|
+
// on every render due to a new array literal being passed each time.
|
|
159
|
+
const observedRefs = useMemo(
|
|
160
|
+
() => [bannerRef, mainRef, topRef, topLeftColRef, topRightColRef, actionsRef, bottomRef, bottomRightRef, leftTopRef, leftBottomRef, rightTopRef, rightBottomRef, drawerRef],
|
|
161
|
+
[]
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
useResizeObserver(observedRefs, () => {
|
|
165
|
+
calculateLayout(layoutRefs)
|
|
160
166
|
})
|
|
161
167
|
}
|
|
@@ -3,6 +3,8 @@ import { useEffect, useRef } from 'react'
|
|
|
3
3
|
export function useResizeObserver (targetRefs, callback) {
|
|
4
4
|
const frameRef = useRef()
|
|
5
5
|
const prevSizes = useRef(new WeakMap()) // track sizes per element
|
|
6
|
+
const callbackRef = useRef(callback)
|
|
7
|
+
callbackRef.current = callback
|
|
6
8
|
|
|
7
9
|
useEffect(() => {
|
|
8
10
|
const refs = Array.isArray(targetRefs) ? targetRefs : [targetRefs]
|
|
@@ -13,6 +15,8 @@ export function useResizeObserver (targetRefs, callback) {
|
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
const observer = new window.ResizeObserver(entries => {
|
|
18
|
+
const changedEntries = []
|
|
19
|
+
|
|
16
20
|
for (const entry of entries) {
|
|
17
21
|
const { width, height } = entry.contentRect
|
|
18
22
|
const prev = prevSizes.current.get(entry.target) || {}
|
|
@@ -22,8 +26,17 @@ export function useResizeObserver (targetRefs, callback) {
|
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
prevSizes.current.set(entry.target, { width, height })
|
|
29
|
+
changedEntries.push(entry)
|
|
30
|
+
}
|
|
25
31
|
|
|
26
|
-
|
|
32
|
+
if (changedEntries.length) {
|
|
33
|
+
// Batch all entries into a single RAF — prevents multiple simultaneous
|
|
34
|
+
// resize events (e.g. panel open) from queueing separate callbacks and
|
|
35
|
+
// causing a ResizeObserver loop under synchronous renderers like preact.
|
|
36
|
+
cancelAnimationFrame(frameRef.current)
|
|
37
|
+
frameRef.current = requestAnimationFrame(() => {
|
|
38
|
+
for (const entry of changedEntries) { callbackRef.current(entry) }
|
|
39
|
+
})
|
|
27
40
|
}
|
|
28
41
|
})
|
|
29
42
|
|
|
@@ -35,7 +48,7 @@ export function useResizeObserver (targetRefs, callback) {
|
|
|
35
48
|
cancelAnimationFrame(frameRef.current)
|
|
36
49
|
}
|
|
37
50
|
}
|
|
38
|
-
}, [targetRefs
|
|
51
|
+
}, [targetRefs]) // callback intentionally omitted — always read from callbackRef
|
|
39
52
|
|
|
40
53
|
return { frameRef }
|
|
41
54
|
}
|
|
@@ -26,6 +26,7 @@ describe('useResizeObserver', () => {
|
|
|
26
26
|
this.trigger = (entries) => cb(entries) // helper to simulate resize
|
|
27
27
|
})
|
|
28
28
|
|
|
29
|
+
jest.spyOn(global, 'requestAnimationFrame').mockImplementation(cb => { cb(); return 0 })
|
|
29
30
|
jest.spyOn(global, 'cancelAnimationFrame').mockImplementation(() => {})
|
|
30
31
|
})
|
|
31
32
|
|
|
@@ -7,6 +7,7 @@ import { useApp } from '../store/appContext.js'
|
|
|
7
7
|
import { useConfig } from '../store/configContext.js'
|
|
8
8
|
import { useEvaluateProp } from '../hooks/useEvaluateProp.js'
|
|
9
9
|
import { useButtonStateEvaluator } from '../hooks/useButtonStateEvaluator.js'
|
|
10
|
+
import { useContinueEnabledEvaluator } from '../hooks/useContinueEnabledEvaluator.js'
|
|
10
11
|
|
|
11
12
|
// Create a component for each plugin to handle its hooks properly
|
|
12
13
|
const PluginInit = ({ plugin, mode }) => {
|
|
@@ -55,6 +56,7 @@ export const PluginInits = () => {
|
|
|
55
56
|
// Evaluate reactive button states globally
|
|
56
57
|
const evaluateProp = useEvaluateProp()
|
|
57
58
|
useButtonStateEvaluator(evaluateProp)
|
|
59
|
+
useContinueEnabledEvaluator()
|
|
58
60
|
|
|
59
61
|
return (
|
|
60
62
|
<>
|
|
@@ -116,6 +116,24 @@ function resolveGroupOrder (group) {
|
|
|
116
116
|
return group.order ?? 0
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
function applySlotExclusivity (matching, appState) {
|
|
120
|
+
let exclusivePluginId = null
|
|
121
|
+
|
|
122
|
+
for (const [id, config] of matching) {
|
|
123
|
+
if (config.exclusiveSlot && !appState.hiddenButtons.has(id) && config.pluginId) {
|
|
124
|
+
if (exclusivePluginId !== null && exclusivePluginId !== config.pluginId) {
|
|
125
|
+
logger.warn(`Slot exclusivity conflict: plugins [${exclusivePluginId}, ${config.pluginId}] are both claiming exclusive slot ownership. Showing all buttons.`)
|
|
126
|
+
return matching
|
|
127
|
+
}
|
|
128
|
+
exclusivePluginId = config.pluginId
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (exclusivePluginId === null) { return matching }
|
|
133
|
+
|
|
134
|
+
return matching.filter(([_, config]) => config.pluginId === exclusivePluginId)
|
|
135
|
+
}
|
|
136
|
+
|
|
119
137
|
function renderButton ({ btn, appState, appConfig, evaluateProp }) {
|
|
120
138
|
const [buttonId, config] = btn
|
|
121
139
|
const bpConfig = config[appState.breakpoint] ?? {}
|
|
@@ -148,7 +166,8 @@ function renderButton ({ btn, appState, appConfig, evaluateProp }) {
|
|
|
148
166
|
function mapButtons ({ slot, appState, appConfig, evaluateProp }) {
|
|
149
167
|
const { buttonConfig, breakpoint } = appState
|
|
150
168
|
|
|
151
|
-
const
|
|
169
|
+
const raw = getMatchingButtons({ appState, appConfig, buttonConfig, slot, evaluateProp })
|
|
170
|
+
const matching = applySlotExclusivity(raw, appState)
|
|
152
171
|
|
|
153
172
|
if (!matching.length) {
|
|
154
173
|
return []
|
|
@@ -243,6 +262,7 @@ function mapButtons ({ slot, appState, appConfig, evaluateProp }) {
|
|
|
243
262
|
export {
|
|
244
263
|
mapButtons,
|
|
245
264
|
getMatchingButtons,
|
|
265
|
+
applySlotExclusivity,
|
|
246
266
|
renderButton,
|
|
247
267
|
resolveGroupName,
|
|
248
268
|
resolveGroupLabel,
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import { mapButtons, getMatchingButtons, renderButton, resolveGroupName, resolveGroupLabel, resolveGroupOrder } from './mapButtons.js'
|
|
2
|
+
import { mapButtons, getMatchingButtons, applySlotExclusivity, renderButton, resolveGroupName, resolveGroupLabel, resolveGroupOrder } from './mapButtons.js'
|
|
3
|
+
import { logger } from '../../services/logger.js'
|
|
3
4
|
import { getPanelConfig } from '../registry/panelRegistry.js'
|
|
4
5
|
|
|
6
|
+
jest.mock('../../services/logger.js', () => ({ logger: { warn: jest.fn() } }))
|
|
5
7
|
jest.mock('../registry/buttonRegistry.js')
|
|
6
8
|
jest.mock('../registry/panelRegistry.js')
|
|
7
9
|
jest.mock('../components/MapButton/MapButton.jsx', () => ({
|
|
@@ -350,6 +352,49 @@ describe('mapButtons module', () => {
|
|
|
350
352
|
expect(map()[0].order).toBe(0)
|
|
351
353
|
})
|
|
352
354
|
|
|
355
|
+
it('filters to exclusive plugin when one plugin has a visible exclusiveSlot button', () => {
|
|
356
|
+
appState.buttonConfig = {
|
|
357
|
+
drawCancel: { ...baseBtn, pluginId: 'draw', exclusiveSlot: true },
|
|
358
|
+
journeyBack: { ...baseBtn }
|
|
359
|
+
}
|
|
360
|
+
const matching = [['drawCancel', appState.buttonConfig.drawCancel], ['journeyBack', appState.buttonConfig.journeyBack]]
|
|
361
|
+
const result = applySlotExclusivity(matching, appState)
|
|
362
|
+
expect(result).toHaveLength(1)
|
|
363
|
+
expect(result[0][0]).toBe('drawCancel')
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
it('returns all buttons when no exclusiveSlot buttons are visible', () => {
|
|
367
|
+
appState.buttonConfig = {
|
|
368
|
+
drawCancel: { ...baseBtn, pluginId: 'draw', exclusiveSlot: true },
|
|
369
|
+
journeyBack: { ...baseBtn }
|
|
370
|
+
}
|
|
371
|
+
appState.hiddenButtons = new Set(['drawCancel'])
|
|
372
|
+
const matching = [['drawCancel', appState.buttonConfig.drawCancel], ['journeyBack', appState.buttonConfig.journeyBack]]
|
|
373
|
+
const result = applySlotExclusivity(matching, appState)
|
|
374
|
+
expect(result).toHaveLength(2)
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
it('returns all buttons when no buttons have exclusiveSlot', () => {
|
|
378
|
+
const matching = [['b1', { ...baseBtn }], ['b2', { ...baseBtn }]]
|
|
379
|
+
expect(applySlotExclusivity(matching, appState)).toHaveLength(2)
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
it('ignores exclusiveSlot on buttons without a pluginId', () => {
|
|
383
|
+
const matching = [['hostBtn', { ...baseBtn, exclusiveSlot: true }]]
|
|
384
|
+
expect(applySlotExclusivity(matching, appState)).toHaveLength(1)
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
it('warns and returns all when multiple plugins claim exclusivity', () => {
|
|
388
|
+
const matching = [
|
|
389
|
+
['drawCancel', { ...baseBtn, pluginId: 'draw', exclusiveSlot: true }],
|
|
390
|
+
['otherBtn', { ...baseBtn, pluginId: 'other', exclusiveSlot: true }]
|
|
391
|
+
]
|
|
392
|
+
const result = applySlotExclusivity(matching, appState)
|
|
393
|
+
expect(result).toHaveLength(2)
|
|
394
|
+
expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('draw'))
|
|
395
|
+
expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('other'))
|
|
396
|
+
})
|
|
397
|
+
|
|
353
398
|
it('excludes menu items from slot rendering even when they have a matching slot', () => {
|
|
354
399
|
appState.buttonConfig = ({
|
|
355
400
|
parent: baseBtn,
|
|
@@ -40,7 +40,7 @@ export const initialState = (config) => {
|
|
|
40
40
|
mode: mode || null,
|
|
41
41
|
previousMode: null,
|
|
42
42
|
safeZoneInset: null,
|
|
43
|
-
disabledButtons: new Set(),
|
|
43
|
+
disabledButtons: config.backAndContinue?.continueLabel ? new Set(['journeyContinue']) : new Set(),
|
|
44
44
|
hiddenButtons: new Set(),
|
|
45
45
|
pressedButtons: new Set(),
|
|
46
46
|
expandedButtons: new Set(),
|
|
@@ -70,6 +70,43 @@ describe('initialState', () => {
|
|
|
70
70
|
expect(result.prefersReducedMotion).toBe(false)
|
|
71
71
|
})
|
|
72
72
|
|
|
73
|
+
test('pre-disables journeyContinue when backAndContinue has a continueLabel', () => {
|
|
74
|
+
mockMedia()
|
|
75
|
+
mockPanels({})
|
|
76
|
+
mockFullscreen(false)
|
|
77
|
+
|
|
78
|
+
const config = {
|
|
79
|
+
behaviour: 'buttonFirst',
|
|
80
|
+
initialBreakpoint: 'md',
|
|
81
|
+
initialInterfaceType: 'desktop',
|
|
82
|
+
appColorScheme: 'light',
|
|
83
|
+
autoColorScheme: false,
|
|
84
|
+
backAndContinue: { continueLabel: 'Continue' },
|
|
85
|
+
...createMockRegistries()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const result = initialState(config)
|
|
89
|
+
expect(result.disabledButtons.has('journeyContinue')).toBe(true)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('does not pre-disable journeyContinue when backAndContinue is null', () => {
|
|
93
|
+
mockMedia()
|
|
94
|
+
mockPanels({})
|
|
95
|
+
mockFullscreen(false)
|
|
96
|
+
|
|
97
|
+
const config = {
|
|
98
|
+
behaviour: 'buttonFirst',
|
|
99
|
+
initialBreakpoint: 'md',
|
|
100
|
+
initialInterfaceType: 'desktop',
|
|
101
|
+
appColorScheme: 'light',
|
|
102
|
+
autoColorScheme: false,
|
|
103
|
+
...createMockRegistries()
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const result = initialState(config)
|
|
107
|
+
expect(result.disabledButtons.has('journeyContinue')).toBe(false)
|
|
108
|
+
})
|
|
109
|
+
|
|
73
110
|
test('defaults mode to null when missing', () => {
|
|
74
111
|
mockMedia({ prefersReducedMotion: false })
|
|
75
112
|
mockPanels({})
|
|
@@ -225,8 +225,8 @@ export default class InteractiveMap {
|
|
|
225
225
|
}
|
|
226
226
|
})
|
|
227
227
|
|
|
228
|
-
updateDOMState(this)
|
|
229
|
-
this.eventBus.emit(events.APP_OPENED, { statePreserved: false })
|
|
228
|
+
const { isFullscreen } = updateDOMState(this)
|
|
229
|
+
this.eventBus.emit(events.APP_OPENED, { statePreserved: false, isFullscreen })
|
|
230
230
|
} catch (err) {
|
|
231
231
|
this._openButton?.removeAttribute('style')
|
|
232
232
|
renderError(this.rootEl, this.config.genericErrorText)
|
|
@@ -304,9 +304,9 @@ export default class InteractiveMap {
|
|
|
304
304
|
this._openButton.style.display = 'none'
|
|
305
305
|
}
|
|
306
306
|
|
|
307
|
-
updateDOMState(this)
|
|
307
|
+
const { isFullscreen } = updateDOMState(this)
|
|
308
308
|
|
|
309
|
-
this.eventBus.emit(events.APP_OPENED, { statePreserved: true })
|
|
309
|
+
this.eventBus.emit(events.APP_OPENED, { statePreserved: true, isFullscreen })
|
|
310
310
|
}
|
|
311
311
|
|
|
312
312
|
/**
|
|
@@ -415,6 +415,16 @@ export default class InteractiveMap {
|
|
|
415
415
|
this.eventBus.emit(events.APP_TOGGLE_BUTTON_STATE, { id, prop, value })
|
|
416
416
|
}
|
|
417
417
|
|
|
418
|
+
/**
|
|
419
|
+
* Enable or disable the journey continue button.
|
|
420
|
+
* Only has effect when backAndContinue is configured.
|
|
421
|
+
*
|
|
422
|
+
* @param {boolean} enabled
|
|
423
|
+
*/
|
|
424
|
+
setContinueEnabled (enabled) {
|
|
425
|
+
this.toggleButtonState('journeyContinue', 'disabled', !enabled)
|
|
426
|
+
}
|
|
427
|
+
|
|
418
428
|
/**
|
|
419
429
|
* Add a panel to the UI.
|
|
420
430
|
*
|
|
@@ -23,7 +23,7 @@ jest.mock('./behaviourController.js', () => ({
|
|
|
23
23
|
setupBehavior: jest.fn(),
|
|
24
24
|
shouldLoadComponent: jest.fn(() => true)
|
|
25
25
|
}))
|
|
26
|
-
jest.mock('./domStateManager.js', () => ({ updateDOMState: jest.fn(), removeLoadingState: jest.fn() }))
|
|
26
|
+
jest.mock('./domStateManager.js', () => ({ updateDOMState: jest.fn().mockReturnValue({ isFullscreen: false }), removeLoadingState: jest.fn() }))
|
|
27
27
|
jest.mock('./renderError.js', () => ({ renderError: jest.fn() }))
|
|
28
28
|
jest.mock('../config/mergeConfig.js', () => ({ mergeConfig: jest.fn(cfg => cfg) }))
|
|
29
29
|
const mockBreakpointDetector = {
|
|
@@ -183,7 +183,7 @@ describe('InteractiveMap — loadApp', () => {
|
|
|
183
183
|
await map.loadApp()
|
|
184
184
|
expect(map.on).toBe(originalOn)
|
|
185
185
|
expect(typeof map.someMethod).toBe('function')
|
|
186
|
-
expect(map.eventBus.emit).toHaveBeenCalledWith('app:opened', { statePreserved: false })
|
|
186
|
+
expect(map.eventBus.emit).toHaveBeenCalledWith('app:opened', { statePreserved: false, isFullscreen: false })
|
|
187
187
|
})
|
|
188
188
|
})
|
|
189
189
|
|
|
@@ -436,7 +436,7 @@ describe('InteractiveMap — hideApp and showApp', () => {
|
|
|
436
436
|
expect(map.rootEl.style.display).toBe('')
|
|
437
437
|
expect(mockButtonInstance.style.display).toBe('none')
|
|
438
438
|
expect(updateDOMState).toHaveBeenCalledWith(map)
|
|
439
|
-
expect(map.eventBus.emit).toHaveBeenCalledWith('app:opened', { statePreserved: true })
|
|
439
|
+
expect(map.eventBus.emit).toHaveBeenCalledWith('app:opened', { statePreserved: true, isFullscreen: false })
|
|
440
440
|
})
|
|
441
441
|
|
|
442
442
|
it('showApp: works without _openButton', () => {
|
|
@@ -563,6 +563,14 @@ describe('InteractiveMap — Public API Methods', () => {
|
|
|
563
563
|
expect(map.eventBus.emit).toHaveBeenCalledWith('app:togglebuttonstate', { id: 'btn-1', prop: 'disabled', value: true })
|
|
564
564
|
})
|
|
565
565
|
|
|
566
|
+
it('setContinueEnabled enables and disables the journey continue button', () => {
|
|
567
|
+
map.setContinueEnabled(true)
|
|
568
|
+
expect(map.eventBus.emit).toHaveBeenCalledWith('app:togglebuttonstate', { id: 'journeyContinue', prop: 'disabled', value: false })
|
|
569
|
+
|
|
570
|
+
map.setContinueEnabled(false)
|
|
571
|
+
expect(map.eventBus.emit).toHaveBeenCalledWith('app:togglebuttonstate', { id: 'journeyContinue', prop: 'disabled', value: true })
|
|
572
|
+
})
|
|
573
|
+
|
|
566
574
|
it('fitToBounds and setView emit correct events', () => {
|
|
567
575
|
const bbox = [-0.489, 51.28, 0.236, 51.686]
|
|
568
576
|
const center = [-0.1276, 51.5074]
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getQueryParam } from '../utils/queryString.js'
|
|
2
2
|
import { isHybridFullscreen } from '../utils/getIsFullscreen.js'
|
|
3
3
|
import { updateDOMState } from './domStateManager.js'
|
|
4
|
+
import { EVENTS } from '../config/events.js'
|
|
4
5
|
|
|
5
6
|
// -----------------------------------------------------------------------------
|
|
6
7
|
// Public API
|
|
@@ -62,7 +63,8 @@ function setupBehavior (mapInstance) {
|
|
|
62
63
|
mapInstance.loadApp()
|
|
63
64
|
} else {
|
|
64
65
|
// Map is showing - update DOM state for fullscreen/inline transition
|
|
65
|
-
updateDOMState(mapInstance)
|
|
66
|
+
const { isFullscreen } = updateDOMState(mapInstance)
|
|
67
|
+
mapInstance.eventBus.emit(EVENTS.APP_FULLSCREEN_CHANGE, { isFullscreen })
|
|
66
68
|
}
|
|
67
69
|
} else if (mapInstance._root) {
|
|
68
70
|
mapInstance.hideApp()
|
|
@@ -7,7 +7,7 @@ import * as queryString from '../utils/queryString.js'
|
|
|
7
7
|
import { updateDOMState } from './domStateManager.js'
|
|
8
8
|
|
|
9
9
|
jest.mock('../utils/queryString.js')
|
|
10
|
-
jest.mock('./domStateManager.js', () => ({ updateDOMState: jest.fn() }))
|
|
10
|
+
jest.mock('./domStateManager.js', () => ({ updateDOMState: jest.fn().mockReturnValue({ isFullscreen: false }) }))
|
|
11
11
|
|
|
12
12
|
describe('shouldLoadComponent', () => {
|
|
13
13
|
beforeEach(() => {
|
|
@@ -67,7 +67,8 @@ describe('setupBehavior', () => {
|
|
|
67
67
|
loadApp: jest.fn(),
|
|
68
68
|
removeApp: jest.fn(),
|
|
69
69
|
hideApp: jest.fn(),
|
|
70
|
-
showApp: jest.fn()
|
|
70
|
+
showApp: jest.fn(),
|
|
71
|
+
eventBus: { emit: jest.fn() }
|
|
71
72
|
}
|
|
72
73
|
// Default: viewport is wide
|
|
73
74
|
window.matchMedia = jest.fn().mockImplementation(() => ({
|
|
@@ -187,14 +188,16 @@ describe('setupBehavior', () => {
|
|
|
187
188
|
expect(mockMapInstance.showApp).not.toHaveBeenCalled()
|
|
188
189
|
})
|
|
189
190
|
|
|
190
|
-
it('calls updateDOMState when map is showing and should load', () => {
|
|
191
|
+
it('calls updateDOMState and emits APP_FULLSCREEN_CHANGE when map is showing and should load', () => {
|
|
191
192
|
mockMapInstance._isHidden = false
|
|
192
193
|
mockMapInstance._root = {} // has root
|
|
193
194
|
queryString.getQueryParam.mockReturnValue(null)
|
|
195
|
+
updateDOMState.mockReturnValue({ isFullscreen: false })
|
|
194
196
|
|
|
195
197
|
handleChange()
|
|
196
198
|
|
|
197
199
|
expect(updateDOMState).toHaveBeenCalledWith(mockMapInstance)
|
|
200
|
+
expect(mockMapInstance.eventBus.emit).toHaveBeenCalledWith('app:fullscreenchange', { isFullscreen: false })
|
|
198
201
|
expect(mockMapInstance.loadApp).not.toHaveBeenCalled()
|
|
199
202
|
expect(mockMapInstance.showApp).not.toHaveBeenCalled()
|
|
200
203
|
})
|