@defra/interactive-map 0.0.21-alpha → 0.0.23-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/dist/css/index.css +1 -1
- package/dist/esm/im-core.js +1 -1
- package/dist/esm/im-shell.js +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/umd/im-core.js +1 -1
- package/dist/umd/index.js +1 -1
- package/docs/api/button-definition.md +39 -0
- package/docs/api/context.md +4 -4
- package/docs/api/map-style-config.md +11 -2
- package/docs/api/marker-config.md +38 -7
- package/docs/api/panel-definition.md +7 -3
- package/docs/api/symbol-config.md +10 -26
- package/docs/api/symbol-registry.md +28 -13
- package/docs/api.md +65 -4
- package/docs/assets/css/{docusaurus.css → docusaurus.scss} +18 -12
- package/docs/assets/images/toggle-marker-label.jpg +0 -0
- package/docs/examples/index.mdx +12 -0
- package/docs/examples/toggle-marker-label.mdx +89 -0
- package/docs/plugins/datasets.md +39 -1
- package/docs/plugins/interact.md +4 -11
- package/docusaurus.config.cjs +2 -1
- package/package.json +10 -12
- package/plugins/beta/datasets/dist/adapters/maplibre/esm/im-datasets-ml-adapter.js +1 -0
- package/plugins/beta/datasets/dist/css/index.css +2 -2
- package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +1 -1
- package/plugins/beta/datasets/dist/umd/maplibre/im-datasets-maplibre-adapter.js +2 -0
- package/plugins/beta/datasets/dist/umd/maplibre/im-datasets-plugin.js +2 -0
- package/plugins/beta/datasets/dist/umd/maplibre/index.js +2 -0
- package/plugins/beta/datasets/src/adapters/maplibre/index.js +1 -1
- package/plugins/beta/datasets/src/adapters/maplibre/layerBuilders.js +5 -5
- package/plugins/beta/datasets/src/adapters/maplibre/maplibreLayerAdapter.js +24 -29
- package/plugins/beta/datasets/src/adapters/maplibre/patternImages.js +2 -2
- package/plugins/beta/datasets/src/adapters/maplibre/symbolImages.js +3 -3
- package/plugins/beta/datasets/src/components/KeySvg.jsx +2 -2
- package/plugins/beta/datasets/src/components/KeySvgPattern.jsx +1 -2
- package/plugins/beta/datasets/src/components/svgProperties.js +2 -2
- package/plugins/beta/datasets/src/index.maplibre.umd.js +23 -0
- package/plugins/beta/datasets/src/panels/Key.module.scss +2 -2
- package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
- 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/map-styles/dist/esm/im-map-styles-plugin.js +1 -1
- package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
- package/plugins/beta/map-styles/dist/umd/index.js +1 -1
- package/plugins/beta/use-location/dist/esm/im-use-location-plugin.js +1 -1
- package/plugins/beta/use-location/dist/umd/im-use-location-plugin.js +1 -1
- package/plugins/beta/use-location/src/manifest.js +1 -0
- 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 +23 -55
- package/plugins/interact/src/InteractInit.test.js +58 -80
- package/plugins/interact/src/defaults.js +1 -3
- package/plugins/interact/src/hooks/useAttachEvents.js +46 -0
- package/plugins/interact/src/hooks/useAttachEvents.test.js +65 -0
- package/plugins/interact/src/hooks/useCrossHairVisibility.js +47 -0
- package/plugins/interact/src/hooks/useCrossHairVisibility.test.js +54 -0
- package/plugins/interact/src/hooks/useHighlightSync.js +30 -8
- package/plugins/interact/src/hooks/useHighlightSync.test.js +127 -80
- package/plugins/interact/src/hooks/useInteractionHandlers.js +18 -17
- package/plugins/interact/src/hooks/useInteractionHandlers.test.js +22 -61
- package/plugins/interact/src/hooks/useMapItemList.js +187 -0
- package/plugins/interact/src/hooks/useMapItemList.test.js +584 -0
- package/plugins/interact/src/index.js +0 -2
- package/plugins/interact/src/index.test.js +0 -2
- package/plugins/interact/src/manifest.js +37 -6
- package/plugins/interact/src/reducer.js +8 -4
- package/plugins/interact/src/reducer.test.js +18 -3
- package/plugins/interact/src/utils/buildStylesMap.js +21 -10
- package/plugins/interact/src/utils/buildStylesMap.test.js +29 -29
- package/plugins/interact/src/utils/spatial.js +0 -15
- package/plugins/interact/src/utils/spatial.test.js +1 -13
- package/plugins/search/dist/css/index.css +1 -1
- package/plugins/search/dist/esm/im-search-plugin.js +1 -1
- package/plugins/search/dist/umd/im-search-plugin.js +1 -1
- package/plugins/search/src/components/CloseButton/CloseButton.module.scss +2 -0
- package/plugins/search/src/components/OpenButton/OpenButton.jsx +1 -1
- package/plugins/search/src/components/OpenButton/OpenButton.module.scss +3 -0
- package/plugins/search/src/search.scss +1 -0
- package/providers/beta/esri/dist/esm/im-esri-provider.js +1 -1
- package/providers/beta/esri/src/esriProvider.js +4 -0
- package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
- package/providers/maplibre/dist/esm/index.js +1 -1
- package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
- package/providers/maplibre/dist/umd/index.js +1 -1
- package/providers/maplibre/src/index.js +7 -1
- package/providers/maplibre/src/index.test.js +15 -2
- package/providers/maplibre/src/mapEvents.js +11 -5
- package/providers/maplibre/src/mapEvents.test.js +14 -3
- package/providers/maplibre/src/maplibreProvider.js +18 -11
- package/providers/maplibre/src/maplibreProvider.test.js +43 -36
- package/providers/maplibre/src/utils/highlightFeatures.js +102 -57
- package/providers/maplibre/src/utils/highlightFeatures.test.js +162 -51
- package/providers/maplibre/src/utils/maplibreFixes.js +10 -0
- package/providers/maplibre/src/utils/maplibreFixes.test.js +31 -0
- package/providers/maplibre/src/utils/patternImages.js +10 -12
- package/providers/maplibre/src/utils/patternImages.test.js +145 -167
- package/providers/maplibre/src/utils/rasteriseToImageData.js +1 -4
- package/providers/maplibre/src/utils/rasteriseToImageData.test.js +23 -24
- package/providers/maplibre/src/utils/symbolImages.js +23 -86
- package/providers/maplibre/src/utils/symbolImages.test.js +60 -105
- package/rollup.esm.mjs +1 -1
- package/src/App/components/Actions/Actions.module.scss +1 -0
- package/src/App/components/Actions/Actions.test.jsx +17 -4
- package/src/App/components/Hints/Hints.jsx +52 -0
- package/src/App/components/Hints/Hints.module.scss +37 -0
- package/src/App/components/Hints/Hints.test.jsx +104 -0
- package/src/App/components/KeyboardHelp/KeyboardHelp.jsx +57 -19
- package/src/App/components/KeyboardHelp/KeyboardHelp.module.scss +10 -0
- package/src/App/components/KeyboardHelp/KeyboardHelp.test.jsx +140 -31
- package/src/App/components/Markers/LabelMarker.jsx +14 -0
- package/src/App/components/Markers/LabelMarker.test.jsx +36 -0
- package/src/App/components/Markers/Markers.jsx +44 -75
- package/src/App/components/Markers/Markers.module.scss +58 -0
- package/src/App/components/Markers/Markers.test.jsx +90 -113
- package/src/App/components/Markers/SymbolLabelMarker.jsx +43 -0
- package/src/App/components/Markers/SymbolLabelMarker.test.jsx +56 -0
- package/src/App/components/Markers/SymbolMarker.jsx +31 -0
- package/src/App/components/Markers/SymbolMarker.test.jsx +72 -0
- package/src/App/components/Panel/Panel.jsx +1 -1
- package/src/App/components/Panel/Panel.module.scss +1 -0
- package/src/App/components/PopupMenu/PopupMenu.test.jsx +117 -1
- package/src/App/components/PopupMenu/usePopupMenu.js +132 -85
- package/src/App/components/Tabs/Tabs.jsx +68 -0
- package/src/App/components/Tabs/Tabs.module.scss +57 -0
- package/src/App/components/Tabs/Tabs.test.jsx +121 -0
- package/src/App/components/Tooltip/Tooltip.module.scss +1 -1
- package/src/App/components/Viewport/Features.jsx +35 -0
- package/src/App/components/Viewport/Features.module.scss +31 -0
- package/src/App/components/Viewport/Features.test.jsx +124 -0
- package/src/App/components/Viewport/MapController.jsx +1 -1
- package/src/App/components/Viewport/MapStatus.jsx +0 -4
- package/src/App/components/Viewport/Viewport.jsx +35 -29
- package/src/App/components/Viewport/Viewport.module.scss +7 -21
- package/src/App/components/Viewport/Viewport.test.jsx +108 -65
- package/src/App/controls/keyboardActions.js +9 -3
- package/src/App/controls/keyboardActions.test.js +122 -87
- package/src/App/controls/keyboardShortcuts.js +1 -14
- package/src/App/hooks/useFeatureFocus.js +199 -0
- package/src/App/hooks/useFeatureFocus.test.js +635 -0
- package/src/App/hooks/useFeatureItems.js +30 -0
- package/src/App/hooks/useFeatureItems.test.js +97 -0
- package/src/App/hooks/useFocusVisible.js +4 -7
- package/src/App/hooks/useFocusVisible.test.js +6 -6
- package/src/App/hooks/useKeyboardHint.js +9 -34
- package/src/App/hooks/useKeyboardHint.test.js +34 -84
- package/src/App/hooks/useKeyboardShortcuts.js +8 -5
- package/src/App/hooks/useKeyboardShortcuts.test.js +16 -12
- package/src/App/hooks/useLayoutMeasurements.js +10 -4
- package/src/App/hooks/useLayoutMeasurements.test.js +30 -0
- package/src/App/hooks/useMapURLSync.js +3 -3
- package/src/App/hooks/useMapURLSync.test.js +20 -3
- package/src/App/hooks/useMarkerCursor.js +33 -0
- package/src/App/hooks/useMarkerCursor.test.js +87 -0
- package/src/App/hooks/useMarkersAPI.js +32 -9
- package/src/App/hooks/useMarkersAPI.test.js +162 -94
- package/src/App/layout/Layout.jsx +2 -0
- package/src/App/layout/Layout.test.jsx +3 -0
- package/src/App/registry/keyboardShortcutRegistry.js +3 -2
- package/src/App/registry/keyboardShortcutRegistry.test.js +6 -8
- package/src/App/renderer/mapButtons.js +5 -1
- package/src/App/renderer/mapButtons.test.js +37 -1
- package/src/App/store/ServiceProvider.jsx +4 -1
- package/src/App/store/ServiceProvider.test.jsx +11 -0
- package/src/App/store/appActionsMap.js +5 -1
- package/src/App/store/appActionsMap.test.js +13 -0
- package/src/App/store/appReducer.js +1 -0
- package/src/App/store/mapActionsMap.js +2 -1
- package/src/InteractiveMap/InteractiveMap.js +13 -1
- package/src/InteractiveMap/InteractiveMap.test.js +6 -4
- package/src/InteractiveMap/behaviourController.js +1 -2
- package/src/InteractiveMap/buttonManager.js +1 -3
- package/src/InteractiveMap/buttonManager.test.js +16 -8
- package/src/InteractiveMap/domStateManager.js +2 -3
- package/src/InteractiveMap/historyManager.js +4 -3
- package/src/config/appConfig.js +3 -1
- package/src/config/appConfig.test.js +4 -0
- package/src/config/defaults.js +3 -2
- package/src/config/events.js +8 -0
- package/src/config/mapTheme.js +8 -4
- package/src/config/mergeConfig.js +8 -2
- package/src/config/mergeConfig.test.js +19 -0
- package/src/config/symbolConfig.js +23 -16
- package/src/scss/main.scss +3 -0
- package/src/scss/tools/_border-focus.scss +0 -1
- package/src/services/eventBus.js +15 -0
- package/src/services/eventBus.test.js +11 -0
- package/src/services/hints.js +47 -0
- package/src/services/hints.test.js +110 -0
- package/src/services/patternRegistry.js +69 -1
- package/src/services/patternRegistry.test.js +112 -1
- package/src/services/reverseGeocode.js +4 -0
- package/src/services/reverseGeocode.test.js +11 -1
- package/src/services/symbolRegistry.js +152 -17
- package/src/services/symbolRegistry.test.js +141 -31
- package/src/types.js +93 -16
- package/src/utils/constrainKeyboardFocus.js +5 -5
- package/src/utils/detectInterfaceType.js +27 -5
- package/src/utils/detectInterfaceType.test.js +29 -3
- package/src/utils/mapStateSync.js +2 -2
- package/src/utils/mapStateSync.test.js +18 -0
- package/src/utils/patternUtils.js +4 -67
- package/src/utils/patternUtils.test.js +1 -123
- package/src/utils/symbolUtils.js +11 -13
- package/src/utils/symbolUtils.test.js +18 -26
- package/webpack.dev.mjs +4 -0
- package/webpack.umd.mjs +9 -6
- package/plugins/beta/datasets/adapters/maplibre/dist/esm/im-datasets-ml-adapter.js +0 -1
- package/plugins/beta/datasets/adapters/maplibre/dist/umd/im-datasets-ml-adapter.js +0 -2
- package/plugins/beta/datasets/adapters/maplibre/dist/umd/index.js +0 -1
- package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +0 -2
- package/plugins/beta/datasets/dist/umd/index.js +0 -2
- package/plugins/interact/dist/css/index.css +0 -1
- package/plugins/interact/src/interact.scss +0 -24
- /package/plugins/beta/datasets/{adapters/maplibre/dist → dist/adapters/maplibre}/esm/index.js +0 -0
- /package/plugins/beta/datasets/{adapters/maplibre/dist/umd/im-datasets-ml-adapter.js.LICENSE.txt → dist/umd/maplibre/im-datasets-maplibre-adapter.js.LICENSE.txt} +0 -0
- /package/plugins/beta/datasets/dist/umd/{im-datasets-plugin.js.LICENSE.txt → maplibre/im-datasets-plugin.js.LICENSE.txt} +0 -0
- /package/plugins/beta/datasets/dist/umd/{index.js.LICENSE.txt → maplibre/index.js.LICENSE.txt} +0 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { renderHook, act } from '@testing-library/react'
|
|
2
|
+
import { useFeatureItems } from './useFeatureItems.js'
|
|
3
|
+
|
|
4
|
+
const SET_FEATURES = 'map:setfeatures' // NOSONAR
|
|
5
|
+
|
|
6
|
+
const makeEventBus = () => {
|
|
7
|
+
const listeners = {}
|
|
8
|
+
return {
|
|
9
|
+
on: jest.fn((e, fn) => { listeners[e] = fn }),
|
|
10
|
+
off: jest.fn(),
|
|
11
|
+
emit: (e, payload) => listeners[e]?.(payload)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// ─── useFeatureItems — initial state ─────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
describe('useFeatureItems — initial state', () => {
|
|
18
|
+
it('returns empty items and multiselectable false before any event', () => {
|
|
19
|
+
const { result } = renderHook(() => useFeatureItems(makeEventBus()))
|
|
20
|
+
expect(result.current.items).toEqual([])
|
|
21
|
+
expect(result.current.multiselectable).toBe(false)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('returns empty items and multiselectable false when eventBus is undefined', () => {
|
|
25
|
+
const { result } = renderHook(() => useFeatureItems(undefined))
|
|
26
|
+
expect(result.current.items).toEqual([])
|
|
27
|
+
expect(result.current.multiselectable).toBe(false)
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
// ─── useFeatureItems — event subscription ────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
describe('useFeatureItems — event subscription', () => {
|
|
34
|
+
it('subscribes to map:setfeatures on mount', () => {
|
|
35
|
+
const eb = makeEventBus()
|
|
36
|
+
renderHook(() => useFeatureItems(eb))
|
|
37
|
+
expect(eb.on).toHaveBeenCalledWith(SET_FEATURES, expect.any(Function))
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('unsubscribes on unmount', () => {
|
|
41
|
+
const eb = makeEventBus()
|
|
42
|
+
const { unmount } = renderHook(() => useFeatureItems(eb))
|
|
43
|
+
unmount()
|
|
44
|
+
expect(eb.off).toHaveBeenCalledWith(SET_FEATURES, expect.any(Function))
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('does not subscribe when eventBus is undefined', () => {
|
|
48
|
+
const spy = jest.spyOn(console, 'error').mockImplementation(() => {})
|
|
49
|
+
renderHook(() => useFeatureItems(undefined))
|
|
50
|
+
spy.mockRestore()
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
// ─── useFeatureItems — items updates ─────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
describe('useFeatureItems — items updates', () => {
|
|
57
|
+
it('updates items when map:setfeatures is emitted', () => {
|
|
58
|
+
const eb = makeEventBus()
|
|
59
|
+
const { result } = renderHook(() => useFeatureItems(eb))
|
|
60
|
+
const items = [{ id: 'a', label: 'Feature A' }, { id: 'b', label: 'Feature B' }]
|
|
61
|
+
act(() => eb.emit(SET_FEATURES, { items }))
|
|
62
|
+
expect(result.current.items).toEqual(items)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('clears items when emitted with an empty array', () => {
|
|
66
|
+
const eb = makeEventBus()
|
|
67
|
+
const { result } = renderHook(() => useFeatureItems(eb))
|
|
68
|
+
act(() => eb.emit(SET_FEATURES, { items: [{ id: 'a', label: 'A' }] }))
|
|
69
|
+
act(() => eb.emit(SET_FEATURES, { items: [] }))
|
|
70
|
+
expect(result.current.items).toEqual([])
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('defaults items to empty array when items key is missing from payload', () => {
|
|
74
|
+
const eb = makeEventBus()
|
|
75
|
+
const { result } = renderHook(() => useFeatureItems(eb))
|
|
76
|
+
act(() => eb.emit(SET_FEATURES, {}))
|
|
77
|
+
expect(result.current.items).toEqual([])
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
// ─── useFeatureItems — multiselectable updates ───────────────────────────────
|
|
82
|
+
|
|
83
|
+
describe('useFeatureItems — multiselectable updates', () => {
|
|
84
|
+
it('sets multiselectable true when emitted with multiselectable: true', () => {
|
|
85
|
+
const eb = makeEventBus()
|
|
86
|
+
const { result } = renderHook(() => useFeatureItems(eb))
|
|
87
|
+
act(() => eb.emit(SET_FEATURES, { items: [], multiselectable: true }))
|
|
88
|
+
expect(result.current.multiselectable).toBe(true)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('defaults multiselectable to false when not present in payload', () => {
|
|
92
|
+
const eb = makeEventBus()
|
|
93
|
+
const { result } = renderHook(() => useFeatureItems(eb))
|
|
94
|
+
act(() => eb.emit(SET_FEATURES, { items: [] }))
|
|
95
|
+
expect(result.current.multiselectable).toBe(false)
|
|
96
|
+
})
|
|
97
|
+
})
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import { useEffect
|
|
1
|
+
import { useEffect } from 'react'
|
|
2
2
|
import { useApp } from '../store/appContext.js'
|
|
3
|
+
import { getInterfaceType } from '../../utils/detectInterfaceType.js'
|
|
3
4
|
|
|
4
5
|
export function useFocusVisible () {
|
|
5
|
-
const {
|
|
6
|
-
const interfaceTypeRef = useRef(interfaceType)
|
|
7
|
-
|
|
8
|
-
// Keep ref in sync - synchronous update ensures it's always current
|
|
9
|
-
interfaceTypeRef.current = interfaceType
|
|
6
|
+
const { layoutRefs } = useApp()
|
|
10
7
|
|
|
11
8
|
useEffect(() => {
|
|
12
9
|
const scope = layoutRefs.appContainerRef.current
|
|
@@ -15,7 +12,7 @@ export function useFocusVisible () {
|
|
|
15
12
|
}
|
|
16
13
|
|
|
17
14
|
function handleFocusIn (e) {
|
|
18
|
-
e.target.dataset.focusVisible =
|
|
15
|
+
e.target.dataset.focusVisible = getInterfaceType() === 'keyboard'
|
|
19
16
|
}
|
|
20
17
|
|
|
21
18
|
function handleFocusOut (e) {
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { renderHook } from '@testing-library/react'
|
|
2
2
|
import { useFocusVisible } from './useFocusVisible'
|
|
3
3
|
import { useApp } from '../store/appContext.js'
|
|
4
|
+
import { getInterfaceType } from '../../utils/detectInterfaceType.js'
|
|
4
5
|
|
|
5
6
|
jest.mock('../store/appContext.js')
|
|
7
|
+
jest.mock('../../utils/detectInterfaceType.js', () => ({
|
|
8
|
+
getInterfaceType: jest.fn(() => 'keyboard')
|
|
9
|
+
}))
|
|
6
10
|
|
|
7
11
|
describe('useFocusVisible', () => {
|
|
8
12
|
let mockAppContainerRef, addEventListenerSpy, removeEventListenerSpy
|
|
@@ -13,8 +17,8 @@ describe('useFocusVisible', () => {
|
|
|
13
17
|
addEventListenerSpy = jest.spyOn(document, 'addEventListener')
|
|
14
18
|
removeEventListenerSpy = jest.spyOn(document, 'removeEventListener')
|
|
15
19
|
|
|
20
|
+
getInterfaceType.mockReturnValue('keyboard')
|
|
16
21
|
useApp.mockReturnValue({
|
|
17
|
-
interfaceType: 'keyboard',
|
|
18
22
|
layoutRefs: { appContainerRef: mockAppContainerRef }
|
|
19
23
|
})
|
|
20
24
|
})
|
|
@@ -26,7 +30,6 @@ describe('useFocusVisible', () => {
|
|
|
26
30
|
|
|
27
31
|
it('returns early when no scope', () => {
|
|
28
32
|
useApp.mockReturnValue({
|
|
29
|
-
interfaceType: 'keyboard',
|
|
30
33
|
layoutRefs: { appContainerRef: { current: null } }
|
|
31
34
|
})
|
|
32
35
|
|
|
@@ -58,10 +61,7 @@ describe('useFocusVisible', () => {
|
|
|
58
61
|
})
|
|
59
62
|
|
|
60
63
|
it('does not set focusVisible on focusin when not keyboard', () => {
|
|
61
|
-
|
|
62
|
-
interfaceType: 'touch',
|
|
63
|
-
layoutRefs: { appContainerRef: mockAppContainerRef }
|
|
64
|
-
})
|
|
64
|
+
getInterfaceType.mockReturnValue('touch')
|
|
65
65
|
|
|
66
66
|
renderHook(() => useFocusVisible())
|
|
67
67
|
|
|
@@ -1,47 +1,26 @@
|
|
|
1
1
|
import { useEffect } from 'react'
|
|
2
|
+
import { getInterfaceType } from '../../utils/detectInterfaceType.js'
|
|
2
3
|
|
|
3
4
|
export function useKeyboardHint ({
|
|
4
|
-
interfaceType,
|
|
5
5
|
containerRef,
|
|
6
|
-
keyboardHintRef,
|
|
7
|
-
keyboardHintVisible,
|
|
8
6
|
onViewportFocusChange
|
|
9
7
|
}) {
|
|
10
|
-
const showHint = keyboardHintVisible
|
|
11
|
-
|
|
12
8
|
useEffect(() => {
|
|
13
|
-
|
|
9
|
+
const el = containerRef.current
|
|
10
|
+
if (!el) {
|
|
14
11
|
return undefined
|
|
15
12
|
}
|
|
16
|
-
|
|
17
|
-
const containerEl = containerRef.current
|
|
18
|
-
|
|
19
13
|
const handleKeyDown = (e) => {
|
|
20
|
-
if (
|
|
21
|
-
onViewportFocusChange(false)
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const handleMouseDown = (e) => {
|
|
26
|
-
const clickedInsideViewport = containerEl.contains(e.target)
|
|
27
|
-
const clickedInsideHint = keyboardHintRef.current?.contains(e.target)
|
|
28
|
-
|
|
29
|
-
if (clickedInsideViewport && !clickedInsideHint) {
|
|
14
|
+
if (e.key === 'Escape') {
|
|
30
15
|
onViewportFocusChange(false)
|
|
31
16
|
}
|
|
32
17
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return () => {
|
|
38
|
-
containerEl.removeEventListener('keydown', handleKeyDown)
|
|
39
|
-
containerEl.removeEventListener('mousedown', handleMouseDown)
|
|
40
|
-
}
|
|
41
|
-
}, [showHint, containerRef, keyboardHintRef, onViewportFocusChange])
|
|
18
|
+
el.addEventListener('keydown', handleKeyDown)
|
|
19
|
+
return () => { el.removeEventListener('keydown', handleKeyDown) }
|
|
20
|
+
}, [containerRef, onViewportFocusChange])
|
|
42
21
|
|
|
43
22
|
const handleFocus = () => {
|
|
44
|
-
if (
|
|
23
|
+
if (getInterfaceType() === 'keyboard') {
|
|
45
24
|
onViewportFocusChange(true)
|
|
46
25
|
}
|
|
47
26
|
}
|
|
@@ -50,9 +29,5 @@ export function useKeyboardHint ({
|
|
|
50
29
|
onViewportFocusChange(false)
|
|
51
30
|
}
|
|
52
31
|
|
|
53
|
-
return {
|
|
54
|
-
showHint,
|
|
55
|
-
handleFocus,
|
|
56
|
-
handleBlur
|
|
57
|
-
}
|
|
32
|
+
return { handleFocus, handleBlur }
|
|
58
33
|
}
|
|
@@ -1,136 +1,86 @@
|
|
|
1
1
|
import { renderHook, act } from '@testing-library/react'
|
|
2
2
|
import { useKeyboardHint } from './useKeyboardHint'
|
|
3
|
+
import { getInterfaceType } from '../../utils/detectInterfaceType.js'
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
})
|
|
5
|
+
jest.mock('../../utils/detectInterfaceType.js', () => ({
|
|
6
|
+
getInterfaceType: jest.fn(() => 'keyboard')
|
|
7
|
+
}))
|
|
8
8
|
|
|
9
9
|
const defaultProps = (overrides = {}) => ({
|
|
10
|
-
|
|
11
|
-
keyboardHintVisible: true,
|
|
10
|
+
containerRef: { current: document.createElement('div') },
|
|
12
11
|
onViewportFocusChange: jest.fn(),
|
|
13
|
-
...createRefs(),
|
|
14
12
|
...overrides
|
|
15
13
|
})
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
test('returns handlers and showHint', () => {
|
|
19
|
-
const { result } = renderHook(() => useKeyboardHint(defaultProps()))
|
|
15
|
+
// ─── return shape ─────────────────────────────────────────────────────────────
|
|
20
16
|
|
|
21
|
-
|
|
17
|
+
describe('useKeyboardHint — return shape', () => {
|
|
18
|
+
test('returns focus and blur handlers', () => {
|
|
19
|
+
const { result } = renderHook(() => useKeyboardHint(defaultProps()))
|
|
22
20
|
expect(typeof result.current.handleFocus).toBe('function')
|
|
23
21
|
expect(typeof result.current.handleBlur).toBe('function')
|
|
24
22
|
})
|
|
23
|
+
})
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
const props = defaultProps({ keyboardHintVisible: false })
|
|
28
|
-
const spy = jest.spyOn(props.containerRef.current, 'addEventListener')
|
|
29
|
-
|
|
30
|
-
renderHook(() => useKeyboardHint(props))
|
|
31
|
-
|
|
32
|
-
expect(spy).not.toHaveBeenCalled()
|
|
33
|
-
spy.mockRestore()
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
test('keydown Escape and Tab trigger onViewportFocusChange(false)', () => {
|
|
37
|
-
const props = defaultProps()
|
|
38
|
-
|
|
39
|
-
renderHook(() => useKeyboardHint(props))
|
|
40
|
-
|
|
41
|
-
const keys = ['Escape', 'Tab']
|
|
42
|
-
keys.forEach(key => {
|
|
43
|
-
act(() => {
|
|
44
|
-
props.containerRef.current.dispatchEvent(
|
|
45
|
-
new KeyboardEvent('keydown', { key })
|
|
46
|
-
)
|
|
47
|
-
})
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
expect(props.onViewportFocusChange).toHaveBeenCalledTimes(2)
|
|
51
|
-
expect(props.onViewportFocusChange).toHaveBeenCalledWith(false)
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
test('keydown with other keys does NOT trigger onViewportFocusChange', () => {
|
|
55
|
-
const props = defaultProps()
|
|
56
|
-
|
|
57
|
-
renderHook(() => useKeyboardHint(props))
|
|
58
|
-
|
|
59
|
-
const keys = ['Enter', 'a']
|
|
60
|
-
keys.forEach(key => {
|
|
61
|
-
act(() => {
|
|
62
|
-
props.containerRef.current.dispatchEvent(
|
|
63
|
-
new KeyboardEvent('keydown', { key })
|
|
64
|
-
)
|
|
65
|
-
})
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
expect(props.onViewportFocusChange).not.toHaveBeenCalled()
|
|
69
|
-
})
|
|
25
|
+
// ─── keydown listener ────────────────────────────────────────────────────────
|
|
70
26
|
|
|
71
|
-
|
|
27
|
+
describe('useKeyboardHint — keydown listener', () => {
|
|
28
|
+
test('Escape calls onViewportFocusChange(false)', () => {
|
|
72
29
|
const props = defaultProps()
|
|
73
|
-
const child = document.createElement('div')
|
|
74
|
-
props.containerRef.current.appendChild(child)
|
|
75
|
-
|
|
76
30
|
renderHook(() => useKeyboardHint(props))
|
|
77
|
-
|
|
78
31
|
act(() => {
|
|
79
|
-
|
|
32
|
+
props.containerRef.current.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }))
|
|
80
33
|
})
|
|
81
|
-
|
|
82
34
|
expect(props.onViewportFocusChange).toHaveBeenCalledWith(false)
|
|
83
35
|
})
|
|
84
36
|
|
|
85
|
-
test('
|
|
37
|
+
test('non-Escape keydown does not call onViewportFocusChange', () => {
|
|
86
38
|
const props = defaultProps()
|
|
87
|
-
props.containerRef.current.appendChild(props.keyboardHintRef.current)
|
|
88
|
-
|
|
89
39
|
renderHook(() => useKeyboardHint(props))
|
|
90
|
-
|
|
91
40
|
act(() => {
|
|
92
|
-
props.
|
|
93
|
-
new MouseEvent('mousedown', { bubbles: true })
|
|
94
|
-
)
|
|
41
|
+
props.containerRef.current.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }))
|
|
95
42
|
})
|
|
96
|
-
|
|
97
43
|
expect(props.onViewportFocusChange).not.toHaveBeenCalled()
|
|
98
44
|
})
|
|
99
45
|
|
|
100
|
-
test('
|
|
46
|
+
test('does nothing when containerRef.current is null', () => {
|
|
47
|
+
const props = defaultProps({ containerRef: { current: null } })
|
|
48
|
+
expect(() => renderHook(() => useKeyboardHint(props))).not.toThrow()
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test('cleanup removes keydown listener on unmount', () => {
|
|
101
52
|
const props = defaultProps()
|
|
102
53
|
const spy = jest.spyOn(props.containerRef.current, 'removeEventListener')
|
|
103
|
-
|
|
104
54
|
const { unmount } = renderHook(() => useKeyboardHint(props))
|
|
105
|
-
|
|
106
55
|
unmount()
|
|
107
|
-
expect(spy).
|
|
56
|
+
expect(spy).toHaveBeenCalledWith('keydown', expect.any(Function))
|
|
108
57
|
})
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
// ─── handleFocus / handleBlur ─────────────────────────────────────────────────
|
|
109
61
|
|
|
110
|
-
|
|
62
|
+
describe('useKeyboardHint — handleFocus / handleBlur', () => {
|
|
63
|
+
beforeEach(() => getInterfaceType.mockReturnValue('keyboard'))
|
|
64
|
+
|
|
65
|
+
test('handleFocus calls onViewportFocusChange(true) for keyboard interface', () => {
|
|
111
66
|
const props = defaultProps()
|
|
112
67
|
const { result } = renderHook(() => useKeyboardHint(props))
|
|
113
|
-
|
|
114
68
|
act(() => result.current.handleFocus())
|
|
115
|
-
|
|
116
69
|
expect(props.onViewportFocusChange).toHaveBeenCalledWith(true)
|
|
117
70
|
})
|
|
118
71
|
|
|
119
|
-
test('handleFocus does not
|
|
120
|
-
|
|
72
|
+
test('handleFocus does not call onViewportFocusChange for non-keyboard interface', () => {
|
|
73
|
+
getInterfaceType.mockReturnValue('mouse')
|
|
74
|
+
const props = defaultProps()
|
|
121
75
|
const { result } = renderHook(() => useKeyboardHint(props))
|
|
122
|
-
|
|
123
76
|
act(() => result.current.handleFocus())
|
|
124
|
-
|
|
125
77
|
expect(props.onViewportFocusChange).not.toHaveBeenCalled()
|
|
126
78
|
})
|
|
127
79
|
|
|
128
|
-
test('handleBlur always
|
|
80
|
+
test('handleBlur always calls onViewportFocusChange(false)', () => {
|
|
129
81
|
const props = defaultProps({ interfaceType: 'mouse' })
|
|
130
82
|
const { result } = renderHook(() => useKeyboardHint(props))
|
|
131
|
-
|
|
132
83
|
act(() => result.current.handleBlur())
|
|
133
|
-
|
|
134
84
|
expect(props.onViewportFocusChange).toHaveBeenCalledWith(false)
|
|
135
85
|
})
|
|
136
86
|
})
|
|
@@ -7,12 +7,13 @@ import { useService } from '../store/serviceContext.js'
|
|
|
7
7
|
|
|
8
8
|
export function useKeyboardShortcuts (containerRef) {
|
|
9
9
|
const { mapProvider, panDelta, nudgePanDelta, zoomDelta, nudgeZoomDelta, readMapText } = useConfig()
|
|
10
|
-
const {
|
|
10
|
+
const { dispatch, layoutRefs } = useApp()
|
|
11
11
|
const { announce } = useService()
|
|
12
12
|
|
|
13
13
|
useEffect(() => {
|
|
14
14
|
const el = containerRef.current
|
|
15
|
-
|
|
15
|
+
const appEl = layoutRefs.appContainerRef?.current
|
|
16
|
+
if (!el || !appEl) {
|
|
16
17
|
return undefined
|
|
17
18
|
}
|
|
18
19
|
|
|
@@ -59,16 +60,18 @@ export function useKeyboardShortcuts (containerRef) {
|
|
|
59
60
|
const handleKeyDown = handle('keydown')
|
|
60
61
|
const handleKeyUp = handle('keyup')
|
|
61
62
|
|
|
63
|
+
// keydown (pan/zoom) stays on the viewport so arrows only fire when the map has focus.
|
|
64
|
+
// keyup (Alt+K and other global shortcuts) attaches to the app container so it fires
|
|
65
|
+
// from anywhere within the app, including the features listbox.
|
|
62
66
|
el.addEventListener('keydown', handleKeyDown)
|
|
63
|
-
|
|
67
|
+
appEl.addEventListener('keyup', handleKeyUp)
|
|
64
68
|
|
|
65
69
|
return () => {
|
|
66
70
|
el.removeEventListener('keydown', handleKeyDown)
|
|
67
|
-
|
|
71
|
+
appEl.removeEventListener('keyup', handleKeyUp)
|
|
68
72
|
}
|
|
69
73
|
}, [
|
|
70
74
|
containerRef,
|
|
71
|
-
interfaceType,
|
|
72
75
|
mapProvider,
|
|
73
76
|
panDelta,
|
|
74
77
|
nudgePanDelta,
|
|
@@ -15,6 +15,7 @@ jest.mock('../store/mapContext.js')
|
|
|
15
15
|
jest.mock('../store/serviceContext.js')
|
|
16
16
|
|
|
17
17
|
const setup = (overrides = {}) => {
|
|
18
|
+
const appContainerRef = { current: document.createElement('div') }
|
|
18
19
|
useConfig.mockReturnValue({
|
|
19
20
|
mapProvider: 'google',
|
|
20
21
|
reverseGeocode: { showMarker: true },
|
|
@@ -25,7 +26,7 @@ const setup = (overrides = {}) => {
|
|
|
25
26
|
readMapText: jest.fn(),
|
|
26
27
|
...overrides.config
|
|
27
28
|
})
|
|
28
|
-
useApp.mockReturnValue({
|
|
29
|
+
useApp.mockReturnValue({ dispatch: jest.fn(), layoutRefs: { appContainerRef }, ...overrides.app })
|
|
29
30
|
useMap.mockReturnValue({ crossHair: { lat: 0, lng: 0 }, ...overrides.map })
|
|
30
31
|
useService.mockReturnValue({ announce: jest.fn(), ...overrides.service })
|
|
31
32
|
|
|
@@ -34,20 +35,22 @@ const setup = (overrides = {}) => {
|
|
|
34
35
|
keyboardMappings.keydown = overrides.keydown || {}
|
|
35
36
|
keyboardMappings.keyup = overrides.keyup || {}
|
|
36
37
|
|
|
37
|
-
return { containerRef: { current: document.createElement('div') }, actions }
|
|
38
|
+
return { containerRef: { current: document.createElement('div') }, appContainerRef, actions }
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
describe('useKeyboardShortcuts', () => {
|
|
41
42
|
beforeEach(jest.clearAllMocks)
|
|
42
43
|
|
|
43
|
-
test('early returns when no container
|
|
44
|
+
test('early returns when no container', () => {
|
|
44
45
|
setup()
|
|
45
46
|
renderHook(() => useKeyboardShortcuts({ current: null }))
|
|
46
47
|
expect(createKeyboardActions).not.toHaveBeenCalled()
|
|
48
|
+
})
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
test('registers shortcuts regardless of interface type', () => {
|
|
51
|
+
const { containerRef } = setup()
|
|
52
|
+
renderHook(() => useKeyboardShortcuts(containerRef))
|
|
53
|
+
expect(createKeyboardActions).toHaveBeenCalled()
|
|
51
54
|
})
|
|
52
55
|
|
|
53
56
|
test('creates actions with correct config', () => {
|
|
@@ -121,7 +124,7 @@ describe('useKeyboardShortcuts', () => {
|
|
|
121
124
|
})
|
|
122
125
|
|
|
123
126
|
test('handles Alt+key combinations and keyup events', () => {
|
|
124
|
-
const { containerRef, actions } = setup({
|
|
127
|
+
const { containerRef, appContainerRef, actions } = setup({
|
|
125
128
|
keydown: { 'Alt+S': 'search' },
|
|
126
129
|
keyup: { ArrowUp: 'stopPan' },
|
|
127
130
|
actions: { search: jest.fn(), stopPan: jest.fn() }
|
|
@@ -131,7 +134,7 @@ describe('useKeyboardShortcuts', () => {
|
|
|
131
134
|
containerRef.current.dispatchEvent(new KeyboardEvent('keydown', { code: 'KeyS', key: 's', altKey: true }))
|
|
132
135
|
expect(actions.search).toHaveBeenCalled()
|
|
133
136
|
|
|
134
|
-
|
|
137
|
+
appContainerRef.current.dispatchEvent(new KeyboardEvent('keyup', { key: 'ArrowUp' }))
|
|
135
138
|
expect(actions.stopPan).toHaveBeenCalled()
|
|
136
139
|
})
|
|
137
140
|
|
|
@@ -156,11 +159,12 @@ describe('useKeyboardShortcuts', () => {
|
|
|
156
159
|
})
|
|
157
160
|
|
|
158
161
|
test('cleanup removes event listeners', () => {
|
|
159
|
-
const { containerRef } = setup()
|
|
160
|
-
const
|
|
162
|
+
const { containerRef, appContainerRef } = setup()
|
|
163
|
+
const viewportSpy = jest.spyOn(containerRef.current, 'removeEventListener')
|
|
164
|
+
const appSpy = jest.spyOn(appContainerRef.current, 'removeEventListener')
|
|
161
165
|
const { unmount } = renderHook(() => useKeyboardShortcuts(containerRef))
|
|
162
166
|
unmount()
|
|
163
|
-
expect(
|
|
164
|
-
expect(
|
|
167
|
+
expect(viewportSpy).toHaveBeenCalledWith('keydown', expect.any(Function))
|
|
168
|
+
expect(appSpy).toHaveBeenCalledWith('keyup', expect.any(Function))
|
|
165
169
|
})
|
|
166
170
|
})
|
|
@@ -55,7 +55,7 @@ function calculateLayout (layoutRefs) {
|
|
|
55
55
|
const {
|
|
56
56
|
appContainerRef, mainRef, topRef, topLeftColRef, topRightColRef,
|
|
57
57
|
bottomRef, attributionsRef, bottomRightRef, leftTopRef, leftBottomRef,
|
|
58
|
-
rightTopRef, rightBottomRef
|
|
58
|
+
rightTopRef, rightBottomRef, actionsRef
|
|
59
59
|
} = layoutRefs
|
|
60
60
|
|
|
61
61
|
const appContainer = appContainerRef.current
|
|
@@ -98,9 +98,15 @@ function calculateLayout (layoutRefs) {
|
|
|
98
98
|
appContainer.style.setProperty('--right-top-max-height', `${rightColumnHeight}px`)
|
|
99
99
|
|
|
100
100
|
// === Keyboard hint bottom offset ===
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
|
|
101
|
+
// On mobile the actions bar is in-flow so baseBottom already accounts for it.
|
|
102
|
+
// On tablet/desktop the actions bar is position:absolute (not in flow), so baseBottom
|
|
103
|
+
// only sees the bottom padding. actionsOffset measures the gap from the actions bar's
|
|
104
|
+
// top edge to the bottom of main, ensuring the hint always clears the floating bar.
|
|
105
|
+
const actionsEl = actionsRef?.current
|
|
106
|
+
const actionsHeight = actionsEl?.offsetHeight ?? 0
|
|
107
|
+
const baseBottom = main.offsetHeight - bottom.offsetTop - bottom.offsetHeight
|
|
108
|
+
const actionsOffset = actionsHeight > 0 ? main.offsetHeight - actionsEl.offsetTop : 0
|
|
109
|
+
appContainer.style.setProperty('--hint-bottom', `${Math.max(baseBottom, actionsOffset + dividerGap)}px`)
|
|
104
110
|
|
|
105
111
|
// === Sub-slot panel max-heights ===
|
|
106
112
|
appContainer.style.setProperty('--left-top-panel-max-height', `${subSlotMaxHeight(leftColumnHeight, buttonHeight(leftBottomRef), dividerGap)}px`)
|
|
@@ -100,6 +100,36 @@ describe('useLayoutMeasurements', () => {
|
|
|
100
100
|
expect(layoutRefs.appContainerRef.current.style.setProperty).toHaveBeenCalledWith(varName, expected)
|
|
101
101
|
})
|
|
102
102
|
|
|
103
|
+
test.each([
|
|
104
|
+
[
|
|
105
|
+
'no actions (height 0) — uses base gap',
|
|
106
|
+
{ main: { offsetHeight: 500 }, bottom: { offsetTop: 400 }, actions: { offsetTop: 450, offsetHeight: 0 } },
|
|
107
|
+
'100px' // baseBottom = 500 - 400 - 0 = 100
|
|
108
|
+
],
|
|
109
|
+
[
|
|
110
|
+
'actions floating above base gap (tablet/desktop) — uses actionsOffset + dividerGap',
|
|
111
|
+
{ main: { offsetHeight: 500 }, bottom: { offsetTop: 440, offsetHeight: 20 }, actions: { offsetTop: 408, offsetHeight: 60 } },
|
|
112
|
+
'100px' // actionsOffset = 500 - 408 = 92, 92 + 8 = 100 > baseBottom (40)
|
|
113
|
+
],
|
|
114
|
+
[
|
|
115
|
+
'base gap already larger than actionsOffset (mobile in-flow) — base gap wins',
|
|
116
|
+
{ main: { offsetHeight: 500 }, bottom: { offsetTop: 350 }, actions: { offsetTop: 430, offsetHeight: 40 } },
|
|
117
|
+
'150px' // baseBottom = 150 > actionsOffset (70) + dividerGap (8) = 78
|
|
118
|
+
]
|
|
119
|
+
])('calculates --hint-bottom for %s', (_, refOverrides, expected) => {
|
|
120
|
+
const { layoutRefs } = setup({ refs: refOverrides })
|
|
121
|
+
renderHook(() => useLayoutMeasurements())
|
|
122
|
+
expect(layoutRefs.appContainerRef.current.style.setProperty).toHaveBeenCalledWith('--hint-bottom', expected)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test('calculates --hint-bottom when actionsRef.current is null', () => {
|
|
126
|
+
const { layoutRefs } = setup()
|
|
127
|
+
layoutRefs.actionsRef.current = null
|
|
128
|
+
renderHook(() => useLayoutMeasurements())
|
|
129
|
+
// actionsHeight = 0, falls back to baseBottom = 500 - 400 - 0 = 100
|
|
130
|
+
expect(layoutRefs.appContainerRef.current.style.setProperty).toHaveBeenCalledWith('--hint-bottom', '100px')
|
|
131
|
+
})
|
|
132
|
+
|
|
103
133
|
test.each([
|
|
104
134
|
[{ offsetWidth: 250 }, { offsetWidth: 200 }, '250px'],
|
|
105
135
|
[{ offsetWidth: 0 }, { offsetWidth: 200 }, '200px'],
|
|
@@ -10,11 +10,11 @@ import { EVENTS as events } from '../../config/events.js'
|
|
|
10
10
|
* shared via a link.
|
|
11
11
|
*/
|
|
12
12
|
export function useMapURLSync () {
|
|
13
|
-
const { id } = useConfig()
|
|
13
|
+
const { id, urlPosition } = useConfig()
|
|
14
14
|
const { eventBus } = useService()
|
|
15
15
|
|
|
16
16
|
useEffect(() => {
|
|
17
|
-
if (!id) {
|
|
17
|
+
if (!id || urlPosition !== 'sync') {
|
|
18
18
|
return undefined
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -27,5 +27,5 @@ export function useMapURLSync () {
|
|
|
27
27
|
|
|
28
28
|
eventBus.on(events.MAP_STATE_UPDATED, handleStateUpdate)
|
|
29
29
|
return () => eventBus.off(events.MAP_STATE_UPDATED, handleStateUpdate)
|
|
30
|
-
}, [id])
|
|
30
|
+
}, [id, urlPosition])
|
|
31
31
|
}
|
|
@@ -25,7 +25,7 @@ describe('useMapURLSync', () => {
|
|
|
25
25
|
})
|
|
26
26
|
|
|
27
27
|
it('registers map:stateupdated listener and cleans up', () => {
|
|
28
|
-
useConfig.mockReturnValue({ id: 'map123' })
|
|
28
|
+
useConfig.mockReturnValue({ id: 'map123', urlPosition: 'sync' })
|
|
29
29
|
|
|
30
30
|
const { unmount } = renderHook(() => useMapURLSync())
|
|
31
31
|
expect(mockEventBus.on).toHaveBeenCalledWith('map:stateupdated', expect.any(Function))
|
|
@@ -35,7 +35,7 @@ describe('useMapURLSync', () => {
|
|
|
35
35
|
})
|
|
36
36
|
|
|
37
37
|
it('calls setMapStateInURL on map:stateupdated', () => {
|
|
38
|
-
useConfig.mockReturnValue({ id: 'map123' })
|
|
38
|
+
useConfig.mockReturnValue({ id: 'map123', urlPosition: 'sync' })
|
|
39
39
|
|
|
40
40
|
renderHook(() => useMapURLSync())
|
|
41
41
|
|
|
@@ -50,7 +50,7 @@ describe('useMapURLSync', () => {
|
|
|
50
50
|
})
|
|
51
51
|
|
|
52
52
|
it('does nothing if id is falsy', () => {
|
|
53
|
-
useConfig.mockReturnValue({ id: null })
|
|
53
|
+
useConfig.mockReturnValue({ id: null, urlPosition: 'sync' })
|
|
54
54
|
|
|
55
55
|
const { unmount } = renderHook(() => useMapURLSync())
|
|
56
56
|
expect(mockEventBus.on).not.toHaveBeenCalled()
|
|
@@ -58,4 +58,21 @@ describe('useMapURLSync', () => {
|
|
|
58
58
|
|
|
59
59
|
unmount() // cleanup should not throw
|
|
60
60
|
})
|
|
61
|
+
|
|
62
|
+
it('does not register listener when urlPosition is none', () => {
|
|
63
|
+
useConfig.mockReturnValue({ id: 'map123', urlPosition: 'none' })
|
|
64
|
+
|
|
65
|
+
const { unmount } = renderHook(() => useMapURLSync())
|
|
66
|
+
expect(mockEventBus.on).not.toHaveBeenCalled()
|
|
67
|
+
|
|
68
|
+
unmount()
|
|
69
|
+
expect(mockEventBus.off).not.toHaveBeenCalled()
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('does not register listener when urlPosition is readOnly', () => {
|
|
73
|
+
useConfig.mockReturnValue({ id: 'map123', urlPosition: 'readOnly' })
|
|
74
|
+
|
|
75
|
+
renderHook(() => useMapURLSync())
|
|
76
|
+
expect(mockEventBus.on).not.toHaveBeenCalled()
|
|
77
|
+
})
|
|
61
78
|
})
|