Skip to content
Open
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
14 changes: 10 additions & 4 deletions plugins/interact/src/hooks/useHighlightSync.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,18 @@ export const useHighlightSync = ({
updateHighlightedFeatures()

// Re-apply after style reload — highlight layers are removed when style reloads.
// MAP_DATA_CHANGE is NOT used here because addLayer/moveLayer fire styledata,
// which would create an infinite update loop via MAP_DATA_CHANGE.
eventBus.on(events.MAP_STYLE_CHANGE, updateHighlightedFeatures)
// MAP_STYLE_CHANGE fires as soon as the new style is ready, before any plugin has
// re-added its layers, so we register a one-time MAP_DATA_CHANGE listener instead.
// MAP_DATA_CHANGE is debounced and fires once all layer additions from every plugin
// have settled, giving us a single well-timed trigger. Using once() avoids the
// infinite loop a permanent listener would cause (layer moves re-trigger MAP_DATA_CHANGE).
const handleStyleChange = () => {
eventBus.once(events.MAP_DATA_CHANGE, updateHighlightedFeatures)
}
eventBus.on(events.MAP_STYLE_CHANGE, handleStyleChange)

return () => {
eventBus.off(events.MAP_STYLE_CHANGE, updateHighlightedFeatures)
eventBus.off(events.MAP_STYLE_CHANGE, handleStyleChange)
}
}, [selectedFeatures, listboxActiveItem, mapProvider, stylesMap, eventBus])
}
21 changes: 15 additions & 6 deletions plugins/interact/src/hooks/useHighlightSync.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,19 @@ jest.mock('../utils/buildStylesMap.js', () => ({
}))

const STYLE_CHANGE = 'map:stylechange'
const DATA_CHANGE = 'map:datachange'

let mockDeps
let capturedEventHandler
let capturedStyleChangeHandler
let capturedDataChangeHandler

const render = (overrides = {}) =>
renderHook(() => useHighlightSync({ ...mockDeps, ...overrides }))

beforeEach(() => {
jest.clearAllMocks()
capturedEventHandler = null
capturedStyleChangeHandler = null
capturedDataChangeHandler = null

mockDeps = {
mapProvider: {
Expand All @@ -28,11 +31,16 @@ beforeEach(() => {
},
selectedFeatures: [],
dispatch: jest.fn(),
events: { MAP_STYLE_CHANGE: STYLE_CHANGE },
events: { MAP_STYLE_CHANGE: STYLE_CHANGE, MAP_DATA_CHANGE: DATA_CHANGE },
eventBus: {
on: jest.fn((event, handler) => {
if (event === STYLE_CHANGE) {
capturedEventHandler = handler
capturedStyleChangeHandler = handler
}
}),
once: jest.fn((event, handler) => {
if (event === DATA_CHANGE) {
capturedDataChangeHandler = handler
}
}),
off: jest.fn()
Expand Down Expand Up @@ -107,13 +115,14 @@ describe('useHighlightSync — styles memoization', () => {
// ─── useHighlightSync — style-change event ───────────────────────────────────

describe('useHighlightSync — style-change event', () => {
it('refreshes highlights on MAP_STYLE_CHANGE', () => {
it('refreshes highlights after MAP_STYLE_CHANGE then MAP_DATA_CHANGE', () => {
mockDeps.selectedFeatures = [{ featureId: 'F1', layerId: 'layer1' }]

render()
mockDeps.mapProvider.updateHighlightedFeatures.mockClear()

act(() => capturedEventHandler())
act(() => capturedStyleChangeHandler())
act(() => capturedDataChangeHandler())

expect(mockDeps.mapProvider.updateHighlightedFeatures).toHaveBeenCalled()
})
Expand Down
15 changes: 15 additions & 0 deletions src/services/eventBus.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,21 @@ class EventBus {
return this
}

/**
* Register a one-time handler that removes itself after the first call.
*
* @param {string} eventName
* @param {Function} handler
* @returns {this}
*/
once (eventName, handler) {
const wrapper = (...args) => {
this.off(eventName, wrapper)
handler(...args)
}
return this.on(eventName, wrapper)
}

/**
* Remove a handler for an event. Omit `handler` to remove all handlers.
*
Expand Down
11 changes: 11 additions & 0 deletions src/services/eventBus.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,17 @@ describe('EventBus singleton', () => {
console.error.mockRestore()
})

it('once fires the handler exactly once', () => {
const handler = jest.fn()
eventBus.once('single', handler)

eventBus.emit('single', 'a')
eventBus.emit('single', 'b')

expect(handler).toHaveBeenCalledTimes(1)
expect(handler).toHaveBeenCalledWith('a')
})

it('destroys all events', () => {
const handler = jest.fn()
eventBus.on('destroyMe', handler)
Expand Down
Loading