Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions plugins/interact/src/hooks/useMapItemList.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ function useActiveItemHandler ({ markers, interactionModes, layers, mapProvider,
dispatch({ type: 'SET_LISTBOX_ACTIVE', payload: null })
return
}
const markerMatch = markers.items.find(m => m.id === id)
if (markerMatch) {
const hasMarkerMatch = markers.items.some(m => m.id === id)
if (hasMarkerMatch) {
listboxActiveItemRef.current = { id, isMarker: true }
dispatch({ type: 'SET_LISTBOX_ACTIVE', payload: null })
return
Expand Down
2 changes: 1 addition & 1 deletion providers/beta/openlayers/src/utils/tileLayers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ describe('createVectorTileLayer', () => {
it('slices capabilities lods to max 16 resolutions', async () => {
await createVectorTileLayer(styleUrl, null)
const { resolutions } = TileGrid.mock.calls[0][0]
expect(resolutions.length).toBe(16)
expect(resolutions).toHaveLength(16)
})

it('creates VectorTileSource with MVT format and 27700 projection', async () => {
Expand Down
12 changes: 6 additions & 6 deletions providers/maplibre/src/utils/queryFeatures.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ describe('queryFeatures coverage', () => {
cases.forEach(({ type, coords, p }) => {
const feat = { id: type, layer: { id: 'L1' }, geometry: { type, coordinates: coords } }
const map = { ...mockMap, queryRenderedFeatures: () => [feat, feat] } // Hits deduplication
expect(queryFeatures(map, p).length).toBe(1)
expect(queryFeatures(map, p)).toHaveLength(1)
})

// 3. Hits Line 144 (.sort) and property-based ID fallback
Expand All @@ -46,7 +46,7 @@ describe('queryFeatures coverage', () => {
const sortMap = { ...mockMap, queryRenderedFeatures: () => [f1, f2] }
const result = queryFeatures(sortMap, { x: 0, y: 0 })

expect(result.length).toBe(2)
expect(result).toHaveLength(2)
expect(result[0].layer.id).toBe('layer-A') // Sorted by layerStack index

// 4. Hit ray-casting intersect logic — point inside the polygon
Expand All @@ -55,23 +55,23 @@ describe('queryFeatures coverage', () => {
geometry: { type: 'Polygon', coordinates: [[[0, 0], [10, 10], [0, 10], [0, 0]]] }
}
const rayMap = { ...mockMap, queryRenderedFeatures: () => [polyFeat] }
expect(queryFeatures(rayMap, { x: 2, y: 8 }).length).toBe(1)
expect(queryFeatures(rayMap, { x: 2, y: 8 })).toHaveLength(1)

// 5. Outside polygon is filtered out (tolerance only applies to lines)
const outsideMap = { ...mockMap, queryRenderedFeatures: () => [polyFeat] }
expect(queryFeatures(outsideMap, { x: -1, y: 5 }).length).toBe(0)
expect(queryFeatures(outsideMap, { x: -1, y: 5 })).toHaveLength(0)

// 6. Symbol under exact click point is included
const symbolFeat = { id: 'sym', layer: { id: 'S', source: 'src' }, geometry: { type: 'Point', coordinates: [0, 0] } }
const symbolMap = { ...mockMap, queryRenderedFeatures: () => [symbolFeat] } // both calls return it
expect(queryFeatures(symbolMap, { x: 5, y: 5 }).length).toBe(1)
expect(queryFeatures(symbolMap, { x: 5, y: 5 })).toHaveLength(1)

// 7. Symbol NOT under exact click point is filtered out
let call = 0
const symbolMissMap = {
...mockMap,
queryRenderedFeatures: () => call++ === 0 ? [symbolFeat] : [] // bbox returns it, exact does not
}
expect(queryFeatures(symbolMissMap, { x: 5, y: 5 }).length).toBe(0)
expect(queryFeatures(symbolMissMap, { x: 5, y: 5 })).toHaveLength(0)
})
})
4 changes: 2 additions & 2 deletions src/App/components/Actions/Actions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const Actions = ({ children }) => {
const { openPanels, panelConfig, breakpoint } = useApp()

const childArray = React.Children.toArray(children)
const visibleChild = childArray.find(c => c.props?.isHidden === false && c.props?.variant !== 'touch')
const hasVisibleChild = childArray.some(c => c.props?.isHidden === false && c.props?.variant !== 'touch')

// If a panel exists above we need so css adjustment
const isBottomSlotUsed = Object.keys(openPanels).some(panelId => {
Expand All @@ -17,7 +17,7 @@ export const Actions = ({ children }) => {
const className = [
'im-c-panel',
'im-c-actions',
!visibleChild && 'im-c-actions--hidden',
!hasVisibleChild && 'im-c-actions--hidden',
isBottomSlotUsed && 'im-c-actions--border-top'
].filter(Boolean).join(' ')

Expand Down
1 change: 1 addition & 0 deletions src/App/components/Tabs/Tabs.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const Tabs = ({ tabs, defaultTab }) => { // NOSONAR: project does not use
<div role='tablist' className='im-c-tabs__list'>
{tabs.map(({ name }) => (
<button
type='button'
key={name}
id={toTabId(name)}
role='tab'
Expand Down
45 changes: 11 additions & 34 deletions src/App/components/Tabs/Tabs.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,40 +77,17 @@ describe('Tabs — WCAG attributes', () => {
// ─── WCAG keyboard navigation ─────────────────────────────────────────────────

describe('Tabs — WCAG keyboard navigation', () => {
it('ArrowRight moves to next tab', () => {
render(<Tabs tabs={[TAB_A, TAB_B, TAB_C]} />)
fireEvent.keyDown(screen.getByRole('tab', { name: 'Alpha' }), { key: 'ArrowRight' })
expect(screen.getByRole('tab', { name: 'Beta' })).toHaveAttribute('aria-selected', 'true')
})

it('ArrowLeft moves to previous tab', () => {
render(<Tabs tabs={[TAB_A, TAB_B, TAB_C]} defaultTab='Beta' />)
fireEvent.keyDown(screen.getByRole('tab', { name: 'Beta' }), { key: 'ArrowLeft' })
expect(screen.getByRole('tab', { name: 'Alpha' })).toHaveAttribute('aria-selected', 'true')
})

it('ArrowRight wraps from last tab to first', () => {
render(<Tabs tabs={[TAB_A, TAB_B, TAB_C]} defaultTab='Gamma' />)
fireEvent.keyDown(screen.getByRole('tab', { name: 'Gamma' }), { key: 'ArrowRight' })
expect(screen.getByRole('tab', { name: 'Alpha' })).toHaveAttribute('aria-selected', 'true')
})

it('ArrowLeft wraps from first tab to last', () => {
render(<Tabs tabs={[TAB_A, TAB_B, TAB_C]} />)
fireEvent.keyDown(screen.getByRole('tab', { name: 'Alpha' }), { key: 'ArrowLeft' })
expect(screen.getByRole('tab', { name: 'Gamma' })).toHaveAttribute('aria-selected', 'true')
})

it('Home moves to first tab', () => {
render(<Tabs tabs={[TAB_A, TAB_B, TAB_C]} defaultTab='Gamma' />)
fireEvent.keyDown(screen.getByRole('tab', { name: 'Gamma' }), { key: 'Home' })
expect(screen.getByRole('tab', { name: 'Alpha' })).toHaveAttribute('aria-selected', 'true')
})

it('End moves to last tab', () => {
render(<Tabs tabs={[TAB_A, TAB_B, TAB_C]} />)
fireEvent.keyDown(screen.getByRole('tab', { name: 'Alpha' }), { key: 'End' })
expect(screen.getByRole('tab', { name: 'Gamma' })).toHaveAttribute('aria-selected', 'true')
it.each([
['ArrowRight moves to next tab', undefined, 'Alpha', 'ArrowRight', 'Beta'],
['ArrowLeft moves to previous tab', 'Beta', 'Beta', 'ArrowLeft', 'Alpha'],
['ArrowRight wraps from last tab to first', 'Gamma', 'Gamma', 'ArrowRight', 'Alpha'],
['ArrowLeft wraps from first tab to last', undefined, 'Alpha', 'ArrowLeft', 'Gamma'],
['Home moves to first tab', 'Gamma', 'Gamma', 'Home', 'Alpha'],
['End moves to last tab', undefined, 'Alpha', 'End', 'Gamma']
])('%s', (_description, defaultTab, focusedTab, key, expectedTab) => {
render(<Tabs tabs={[TAB_A, TAB_B, TAB_C]} defaultTab={defaultTab} />)
fireEvent.keyDown(screen.getByRole('tab', { name: focusedTab }), { key })
expect(screen.getByRole('tab', { name: expectedTab })).toHaveAttribute('aria-selected', 'true')
})

it('unhandled keys do not change the active tab', () => {
Expand Down
2 changes: 1 addition & 1 deletion src/App/hooks/useMarkersAPI.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ describe('useMarkers — cleanup', () => {
let cleanup
act(() => { cleanup = result.current.markerRef('m1')(ctx.mockElement) })
const updateCallback = ctx.mockEventBus.on.mock.calls.find(call => call[0] === 'map:render')[1]
act(() => { if (cleanup) cleanup() })
if (cleanup) cleanup()
expect(ctx.mockEventBus.off).toHaveBeenCalledWith('map:render', updateCallback)
})

Expand Down
4 changes: 2 additions & 2 deletions src/App/registry/pluginRegistry.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,10 +178,10 @@ describe('pluginRegistry', () => {

pluginRegistry.registerPlugin(pluginA)
pluginRegistry.registerPlugin(pluginB)
expect(pluginRegistry.registeredPlugins.length).toBe(2)
expect(pluginRegistry.registeredPlugins).toHaveLength(2)

pluginRegistry.clear()
expect(pluginRegistry.registeredPlugins.length).toBe(0)
expect(pluginRegistry.registeredPlugins).toHaveLength(0)
expect(pluginRegistry.registeredPlugins).toEqual([])
})
})
16 changes: 8 additions & 8 deletions src/App/renderer/mapButtons.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ describe('mapButtons module', () => {
// -------------------------
describe('getMatchingButtons', () => {
const testFilter = (config, expected) => {
expect(getMatchingButtons({ buttonConfig: config, slot: 'header', appState, evaluateProp }).length).toBe(expected)
expect(getMatchingButtons({ buttonConfig: config, slot: 'header', appState, evaluateProp })).toHaveLength(expected)
}

it('returns empty array when buttonConfig is null', () => testFilter(null, 0))
Expand All @@ -117,41 +117,41 @@ describe('mapButtons module', () => {
it('filters out buttons with inline:false when not in fullscreen', () => {
const config = { b1: { ...baseBtn, inline: false } }
const state = { ...appState, isFullscreen: false }
expect(getMatchingButtons({ buttonConfig: config, slot: 'header', appState: state, evaluateProp }).length).toBe(0)
expect(getMatchingButtons({ buttonConfig: config, slot: 'header', appState: state, evaluateProp })).toHaveLength(0)
})

it('includes buttons with inline:false when in fullscreen', () => {
const config = { b1: { ...baseBtn, inline: false } }
const state = { ...appState, isFullscreen: true }
expect(getMatchingButtons({ buttonConfig: config, slot: 'header', appState: state, evaluateProp }).length).toBe(1)
expect(getMatchingButtons({ buttonConfig: config, slot: 'header', appState: state, evaluateProp })).toHaveLength(1)
})

it('includes buttons without inline property regardless of fullscreen state', () => {
const config = { b1: baseBtn }
const state = { ...appState, isFullscreen: false }
expect(getMatchingButtons({ buttonConfig: config, slot: 'header', appState: state, evaluateProp }).length).toBe(1)
expect(getMatchingButtons({ buttonConfig: config, slot: 'header', appState: state, evaluateProp })).toHaveLength(1)
})

it('filters out buttons with isMenuItem:true', () => {
const config = { b1: { ...baseBtn, isMenuItem: true } }
expect(getMatchingButtons({ buttonConfig: config, slot: 'header', appState, evaluateProp }).length).toBe(0)
expect(getMatchingButtons({ buttonConfig: config, slot: 'header', appState, evaluateProp })).toHaveLength(0)
})

it('does not filter out buttons without isMenuItem', () => {
const config = { b1: baseBtn, b2: { ...baseBtn, isMenuItem: false } }
expect(getMatchingButtons({ buttonConfig: config, slot: 'header', appState, evaluateProp }).length).toBe(2)
expect(getMatchingButtons({ buttonConfig: config, slot: 'header', appState, evaluateProp })).toHaveLength(2)
})

it('filters out panel-toggle button when panel is open and non-dismissible at current breakpoint', () => {
const state = { ...appState, panelConfig: { myPanel: { desktop: { open: true, dismissible: false } } } }
const config = { b1: { ...baseBtn, panelId: 'myPanel' } }
expect(getMatchingButtons({ buttonConfig: config, slot: 'header', appState: state, evaluateProp }).length).toBe(0)
expect(getMatchingButtons({ buttonConfig: config, slot: 'header', appState: state, evaluateProp })).toHaveLength(0)
})

it('includes panel-toggle button when panel is dismissible at current breakpoint', () => {
const state = { ...appState, panelConfig: { myPanel: { desktop: { open: true, dismissible: true } } } }
const config = { b1: { ...baseBtn, panelId: 'myPanel' } }
expect(getMatchingButtons({ buttonConfig: config, slot: 'header', appState: state, evaluateProp }).length).toBe(1)
expect(getMatchingButtons({ buttonConfig: config, slot: 'header', appState: state, evaluateProp })).toHaveLength(1)
})
})

Expand Down
Loading