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
30 changes: 23 additions & 7 deletions __mocks__/azure-maps-control.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
class DataSource {
id
options

constructor(id, options) {
this.id = id
this.options = options
}

add = jest.fn()
clear = jest.fn()
remove = jest.fn()
importDataFromUrl = jest.fn()
setOptions = jest.fn()
setOptions = jest.fn((options) => (this.options = options))
getId = () => this.id
}

module.exports = {
Expand All @@ -12,9 +21,14 @@ module.exports = {
add: jest.fn()
},
events: {
add: jest.fn((eventName, callback = () => {}) => {
callback()
})
add: jest.fn((_eventName, _targetOrCallback, callback = () => {}) => {
if (typeof _targetOrCallback === 'function') {
_targetOrCallback()
} else {
callback()
}
}),
remove: jest.fn((eventName) => {})
},
imageSprite: {
add: jest.fn(),
Expand All @@ -26,7 +40,9 @@ module.exports = {
},
layers: {
add: jest.fn(),
remove: jest.fn()
remove: jest.fn(),
getLayers: jest.fn(() => []),
getLayerById: jest.fn()
},
popups: {
getPopups: jest.fn(() => []),
Expand Down Expand Up @@ -123,7 +139,7 @@ module.exports = {
VectorTileSource: jest.fn((id, options) => ({
getId: jest.fn(() => id),
getOptions: jest.fn(() => options)
}))
}))
},
Shape: jest.fn(() => ({
setCoordinates: jest.fn(),
Expand All @@ -132,7 +148,7 @@ module.exports = {
data: {
Position: jest.fn((...args) => args),
BoundingBox: jest.fn((...args) => args),
Point: jest.fn(coords => ({ coords, type: 'Point' })),
Point: jest.fn((coords) => ({ coords, type: 'Point' })),
MultiPoint: jest.fn((coords, bbox) => ({ coords, bbox, type: 'MultiPoint' })),
LineString: jest.fn((coords, bbox) => ({ coords, bbox, type: 'LineString' })),
MultiLineString: jest.fn((multipleCoordinates, bbox) => ({
Expand Down
16 changes: 15 additions & 1 deletion src/components/helpers/mapHelper.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import atlas from 'azure-maps-control'
import { DataSourceType, MapType } from '../../types'

export const generateLinesFromArrayOfPosition = (coordinates: atlas.data.Position[]): atlas.data.LineString => {
export const generateLinesFromArrayOfPosition = (
coordinates: atlas.data.Position[]
): atlas.data.LineString => {
const line = new atlas.data.LineString(coordinates)
return line
}
Expand All @@ -9,3 +12,14 @@ export const generatePixelHeading = (origin: atlas.Pixel, destination: atlas.Pix
const heading = atlas.Pixel.getHeading(origin, destination)
return heading
}

export const getLayersDependingOnDatasource = (mref: MapType, dst: DataSourceType) => {
return mref.layers.getLayers().filter((l) => {
if ((l as atlas.layer.SymbolLayer).getSource) {
const sourceLayer = (l as atlas.layer.SymbolLayer).getSource()
const dsId = typeof sourceLayer === 'string' ? sourceLayer : sourceLayer.getId()
return dsId === dst.getId()
}
return false
})
}
41 changes: 39 additions & 2 deletions src/contexts/AzureMapDataSourceContext.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useContext } from 'react'
import { renderHook } from '@testing-library/react'
import atlas, { Map } from 'azure-maps-control'
import atlas, { layer, Map } from 'azure-maps-control'
import React from 'react'
import { AzureMapsContext } from '../contexts/AzureMapContext'
import {
Expand Down Expand Up @@ -93,12 +93,49 @@ describe('AzureMapDataSourceProvider tests', () => {
})

it('should call setOptions and clear method if options was changed', () => {
const { result, rerender } = renderHook(() => useContextConsumer(), {
const { result } = renderHook(() => useContextConsumer(), {
wrapper: wrapWithDataSourceContext({ id: 'id', options: { option: 'option' } })
})
expect(result.current.dataSourceRef).toBeInstanceOf(atlas.source.DataSource)
expect(
(result.current.dataSourceRef as atlas.source.DataSource).setOptions
).toHaveBeenLastCalledWith({ option: 'option' })
})

it('should remove data source from the map ref on unmount', () => {
mapRef.events.remove = jest.fn()
const events = { render: () => {} }
const { unmount, result } = renderHook(() => useContextConsumer(), {
wrapper: wrapWithDataSourceContext({ id: 'id', options: { option: 'option' }, events })
})

unmount()

expect(mapRef.sources.remove).toHaveBeenCalledWith(result.current.dataSourceRef)
expect(mapRef.events.remove).toHaveBeenCalledWith(
'render',
result.current.dataSourceRef,
events.render
)
})

it('should remove all layers that are using the same datasource from the map ref on unmount', () => {
const dsToBeRemoved = new atlas.source.DataSource('ds_to_be_removed')
const dsToKeep = new atlas.source.DataSource('ds_to_keep')

const symbolLayer = new layer.SymbolLayer(dsToBeRemoved, 'layer_to_be_removed')
const bubbleLayer = new layer.BubbleLayer(dsToKeep, 'layer_to_keep')

symbolLayer.getSource = jest.fn(() => dsToBeRemoved)

mapRef.layers.getLayers = jest.fn(() => [symbolLayer, bubbleLayer])

const { unmount } = renderHook(() => useContextConsumer(), {
wrapper: wrapWithDataSourceContext({ id: dsToBeRemoved.getId() })
})

unmount()
expect(mapRef.layers.remove).toHaveBeenCalledTimes(1)
expect(mapRef.layers.remove).toHaveBeenNthCalledWith(1, 'layer_to_be_removed')
})
})
15 changes: 14 additions & 1 deletion src/contexts/AzureMapDataSourceContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import atlas from 'azure-maps-control'
import { AzureMapsContext } from './AzureMapContext'
import { useCheckRef } from '../hooks/useCheckRef'
import { getLayersDependingOnDatasource } from '../components/helpers/mapHelper'

const AzureMapDataSourceContext = createContext<IAzureMapDataSourceProps>({
dataSourceRef: null
Expand All @@ -31,7 +32,9 @@ const AzureMapDataSourceStatefulProvider = ({
dataFromUrl,
collection
}: IAzureDataSourceStatefulProviderProps) => {
const [dataSourceRef] = useState<atlas.source.DataSource>(new atlas.source.DataSource(id, options))
const [dataSourceRef] = useState<atlas.source.DataSource>(
new atlas.source.DataSource(id, options)
)
const { mapRef } = useContext<IAzureMapsContextProps>(AzureMapsContext)
useCheckRef<MapType, DataSourceType>(mapRef, dataSourceRef, (mref, dref) => {
for (const eventType in events || {}) {
Expand All @@ -46,6 +49,16 @@ const AzureMapDataSourceStatefulProvider = ({
dref.add(collection)
}
}
return () => {
for (const eventType in events || {}) {
mref.events.remove(eventType as any, dref, events[eventType])
}

getLayersDependingOnDatasource(mref, dref).forEach((l) => {
mref.layers.remove(l.getId() ? l.getId() : l)
})
mref.sources.remove(dref)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @rvdkooy, this is great!

Do you mind apply the same fix to AzureMapVectorTileSourceProvider.tsx as well? It can be tested with react-azure-maps-playground under http://localhost:3000/custom-traffic

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry, wasn't able to have a look at it for a while.
I also fixed it in AzureMapVectorTileSourceProvider

Gr,
R

}
})

useEffect(() => {
Expand Down
39 changes: 38 additions & 1 deletion src/contexts/AzureMapVectorTileSourceProvider.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { renderHook } from '@testing-library/react'
import React, { useContext } from 'react'
import { Map } from 'azure-maps-control'
import atlas, { layer, Map } from 'azure-maps-control'
import { IAzureVectorTileSourceStatefulProviderProps } from '../types'
import { AzureMapsContext } from './AzureMapContext'
import { AzureMapVectorTileSourceProvider } from './AzureMapVectorTileSourceProvider'
Expand Down Expand Up @@ -71,4 +71,41 @@ describe('AzureMapVectorTileSourceProvider tests', () => {
expect.any(Function)
)
})

it('should remove data source from the map ref on unmount', () => {
mapRef.events.remove = jest.fn()
const events = { sourceadded: () => {} }
const { unmount, result } = renderHook(() => useContextConsumer(), {
wrapper: wrapWithVectorTileSourceContext({ id: 'id', options: { option: 'option' }, events })
})

unmount()

expect(mapRef.sources.remove).toHaveBeenCalledWith(result.current.dataSourceRef)
expect(mapRef.events.remove).toHaveBeenCalledWith(
'sourceadded',
result.current.dataSourceRef,
events.sourceadded
)
})

it('should remove all layers that are using the same datasource from the map ref on unmount', () => {
const dsToBeRemoved = new atlas.source.DataSource('ds_to_be_removed')
const dsToKeep = new atlas.source.DataSource('ds_to_keep')

const symbolLayer = new layer.SymbolLayer(dsToBeRemoved, 'layer_to_be_removed')
const bubbleLayer = new layer.BubbleLayer(dsToKeep, 'layer_to_keep')

symbolLayer.getSource = jest.fn(() => dsToBeRemoved)

mapRef.layers.getLayers = jest.fn(() => [symbolLayer, bubbleLayer])

const { unmount } = renderHook(() => useContextConsumer(), {
wrapper: wrapWithVectorTileSourceContext({ id: dsToBeRemoved.getId() })
})

unmount()
expect(mapRef.layers.remove).toHaveBeenCalledTimes(1)
expect(mapRef.layers.remove).toHaveBeenNthCalledWith(1, 'layer_to_be_removed')
})
})
41 changes: 34 additions & 7 deletions src/contexts/AzureMapVectorTileSourceProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import atlas from 'azure-maps-control'
import React, { useContext, useState } from 'react'
import { useCheckRef } from '../hooks/useCheckRef'
import { DataSourceType, IAzureMapsContextProps, IAzureMapSourceEventType, IAzureVectorTileSourceStatefulProviderProps, MapType } from '../types'
import {
DataSourceType,
IAzureMapsContextProps,
IAzureMapSourceEventType,
IAzureVectorTileSourceStatefulProviderProps,
MapType
} from '../types'
import { AzureMapDataSourceRawProvider as Provider } from './AzureMapDataSourceContext'
import { AzureMapsContext } from './AzureMapContext'
import { getLayersDependingOnDatasource } from '../components/helpers/mapHelper'

/**
* @param id datasource identifier
Expand All @@ -15,29 +22,49 @@ const AzureMapVectorTileSourceStatefulProvider = ({
id,
children,
options,
events = {},
events = {}
}: IAzureVectorTileSourceStatefulProviderProps) => {
const [dataSourceRef] = useState<atlas.source.VectorTileSource>(new atlas.source.VectorTileSource(id, options))
const [dataSourceRef] = useState<atlas.source.VectorTileSource>(
new atlas.source.VectorTileSource(id, options)
)
const { mapRef } = useContext<IAzureMapsContextProps>(AzureMapsContext)
useCheckRef<MapType, DataSourceType>(mapRef, dataSourceRef, (mref, dref) => {
for (const eventType in events) {
const handler = events[eventType as IAzureMapSourceEventType] as (e: atlas.source.Source) => void | undefined
if(handler) {
const handler = events[eventType as IAzureMapSourceEventType] as (
e: atlas.source.Source
) => void | undefined
if (handler) {
mref.events.add(eventType as IAzureMapSourceEventType, dref, handler)
}
}
mref.sources.add(dref)

return () => {
for (const eventType in events || {}) {
const handler = events[eventType as IAzureMapSourceEventType] as (
e: atlas.source.Source
) => void | undefined
if (handler) {
mref.events.remove(eventType as IAzureMapSourceEventType, dref, handler)
}
}

getLayersDependingOnDatasource(mref, dref).forEach((l) => {
mref.layers.remove(l.getId() ? l.getId() : l)
})
mref.sources.remove(dref)
}
})

return (
<Provider
value={{
dataSourceRef,
dataSourceRef
}}
>
{mapRef && children}
</Provider>
)
}

export { AzureMapVectorTileSourceStatefulProvider as AzureMapVectorTileSourceProvider }
export { AzureMapVectorTileSourceStatefulProvider as AzureMapVectorTileSourceProvider }
7 changes: 5 additions & 2 deletions src/hooks/useAzureMapLayer.test.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import atlas, { source, layer } from 'azure-maps-control'
import { source, layer } from 'azure-maps-control'
import { ReactNode } from 'react'
import { renderHook } from '@testing-library/react'
import { useAzureMapLayer } from './useAzureMapLayer'
import { Map } from 'azure-maps-control'
import React from 'react'
import { AzureMapsContext } from '../contexts/AzureMapContext'
import { AzureMapDataSourceContext } from '../contexts/AzureMapDataSourceContext'
import { IAzureLayerStatefulProviderProps, LayerType } from '../types'
import { IAzureLayerStatefulProviderProps } from '../types'

const mapContextProps = {
mapRef: null,
Expand All @@ -16,6 +16,7 @@ const mapContextProps = {
setMapRef: jest.fn()
}
const mapRef = new Map('fake', {})
mapRef.layers.getLayerById = jest.fn().mockImplementation(() => null)

const wrapWithAzureMapContext = ({ children }: { children?: ReactNode | null }) => {
const datasourceRef = {} as source.DataSource
Expand Down Expand Up @@ -109,6 +110,8 @@ describe('useAzureMapLayer tests', () => {
})

it('shouldRemove layer from map on unmoun', () => {
const symbolLayer = {} as layer.SymbolLayer
mapRef.layers.getLayerById = jest.fn().mockImplementation(() => symbolLayer)
mapRef.layers.remove = jest.fn()
const { unmount } = renderHook(
() =>
Expand Down
4 changes: 3 additions & 1 deletion src/hooks/useAzureMapLayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ export const useAzureMapLayer = ({
mref.layers.add(lref)
return () => {
try {
mref.layers.remove(lref.getId() ? lref.getId() : lref)
if (mref.layers.getLayerById(lref.getId())) {
mref.layers.remove(lref.getId() ? lref.getId() : lref)
}
} catch (e) {
console.error('Error on remove layer', e)
}
Expand Down