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
2 changes: 1 addition & 1 deletion demo/js/esri-datasets.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,5 +145,5 @@ const testGlobalVisibility = () => {
}

interactiveMap.on('datasets:ready', function () {
testGlobalVisibility()
// testGlobalVisibility()
})
41 changes: 10 additions & 31 deletions plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import VectorTileLayer from '@arcgis/core/layers/VectorTileLayer.js'
import GroupLayer from '@arcgis/core/layers/GroupLayer.js'
import { LayerAdapter } from '../layerAdapter.js'
import { datasetRegistry } from '../../registry/datasetRegistry.js'
import { EsriDataset } from './registry/esriDataset.js'

export default class EsriLayerAdapter {
export default class EsriLayerAdapter extends LayerAdapter {
constructor (mapProvider, symbolRegistry, patternRegistry) {
super()
this._mapProvider = mapProvider
this._map = mapProvider.map
// TODO: Implement symbolRegistry and patternRegistry usage in the adapter
Expand All @@ -27,38 +29,22 @@ export default class EsriLayerAdapter {
return new EsriDataset(datasetDefinition)
}

async init (mapStyle) {
async init () {
const topLevelDatasets = datasetRegistry.topLevelDatasets()
// ensure the datasets are added in order
for await (const registryDataset of topLevelDatasets) {
await this._addLayers(registryDataset, mapStyle)
await this._addLayers(registryDataset)
}

// onMapStyleChange: handles showing and hiding sublayers based on the mapStyle
// onMapStyleChange: handles showing and hiding sublayers based on the current mapStyle
// and updating the paint properties of the layers based on the dataset/mapStyle style
await this.onMapStyleChange(datasetRegistry.mapStyle, null)
await this.onMapStyleChange()

// Apply opacity to all layers
await this.applyGlobalOpacity()

// Finally show all layers that are visible based on the dataset/mapStyle visibility
await Promise.all(topLevelDatasets.map(registryDataset => this.applyDatasetVisibility(registryDataset.id)))

// Log the layers for debugging
// this.logLayers('_vectorTileLayers')
// console.log('this._vectorTileOpacityLayers', this._vectorTileOpacityLayers)
// console.log('this._groupLayers', this._groupLayers)
}

logLayers (name) {
const obj = this[name]
console.log(`${name}:`)
Object.entries(obj).forEach(([datasetId, layer]) => {
console.log(` ${datasetId}`)
layer.styleRepository.layers.forEach(styleLayer => {
console.log(` ${styleLayer.id}`)
})
})
}

_addGroupLayer (esriGroupId) {
Expand All @@ -75,7 +61,7 @@ export default class EsriLayerAdapter {
return this._groupLayers[esriGroupId]
}

async _addLayers (registryDataset, mapStyle) {
async _addLayers (registryDataset) {
const { esriGroupId } = registryDataset
const vectorTileParent = esriGroupId ? this._addGroupLayer(esriGroupId) : this._map
const vectorTileLayer = new VectorTileLayer({
Expand Down Expand Up @@ -163,14 +149,7 @@ export default class EsriLayerAdapter {
console.log('TODO: applyFeatureFilter', args)
}

async onMapStyleChange (newMapStyle, dynamicSources) {
if (datasetRegistry.mapStyle.id !== newMapStyle.id) {
// Ensure the datasetRegistry is aware of the new mapStyle so that the visibility and style properties of the datasets can be correctly applied
// TODO - this is a bit of a hack, we should probably have a better way to handle this
// such as having DatasetsInit listen for a mapStyle change event and then call datasetRegistry.attach with the new mapStyle
// and finally trigger this method
datasetRegistry.attach(datasetRegistry.datasets, datasetRegistry._orderedDatasets, newMapStyle)
}
async onMapStyleChange () {
datasetRegistry.forEach(registryDataset => {
const { id, isSublayer, esriStyleLayerId, parent } = registryDataset
const vectorTileLayer = this._vectorTileLayers[isSublayer ? parent.id : id]
Expand All @@ -193,5 +172,5 @@ export default class EsriLayerAdapter {
}

// onMapSizeChange is not applicable to the esriLayerAdapter
async onMapSizeChange (_mapStyle) {}
async onMapSizeChange () {}
}
180 changes: 180 additions & 0 deletions plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import { EsriDataset } from './registry/esriDataset.js'
import EsriLayerAdapter from './esriLayerAdapter.js'
import { datasetRegistry } from '../../registry/datasetRegistry.js'

jest.mock('../../registry/datasetRegistry.js')
jest.mock('@arcgis/core/layers/VectorTileLayer.js', () =>
jest.fn().mockImplementation((opts = {}) => ({
...opts,
add: jest.fn(),
when: jest.fn().mockResolvedValue(undefined),
setStyleLayerVisibility: jest.fn(),
getPaintProperties: jest.fn().mockReturnValue({}),
setPaintProperties: jest.fn()
}))
)
jest.mock('@arcgis/core/layers/GroupLayer.js', () =>
jest.fn().mockImplementation((opts = {}) => ({
...opts,
add: jest.fn()
}))
)

// Uncovered: 80-88,124,145-149

const MAP_STYLE = { id: 'outdoor' }
const makeMap = () => {
const _added = {}
return {
add: jest.fn(({ id }) => {
// Ensure that the same layer is never added twice
expect(_added[id]).toBeUndefined()
_added[id] = true
})
}
}
const makeMapProvider = (map) => ({ map })

describe('esriLayerAdapter', () => {
let map, mapProvider, adapter

beforeEach(() => {
datasetRegistry.useEsriDatasets()
datasetRegistry.attachMapStyle(MAP_STYLE)
datasetRegistry.attachCreateDataset(def => new EsriDataset(def))
map = makeMap()
mapProvider = makeMapProvider(map)
adapter = new EsriLayerAdapter(mapProvider, null, null)
})

// ─── createDataset ───────────────────────────────────────────────────────────

describe('createDataset', () => {
it('returns an EsriDataset instance', () => {
expect(adapter.createDataset({ id: 'test' })).toBeInstanceOf(EsriDataset)
})
})

// ─── applyDatasetVisibility ──────────────────────────────────────────────────

describe('applyDatasetVisibility', () => {
beforeEach(async () => {
const standAlone = datasetRegistry.getDataset('esri-standalone')
await adapter._addLayers(standAlone, MAP_STYLE)
})

it('applies visibility for a known dataset', async () => {
await adapter.applyDatasetVisibility('esri-standalone')
expect(adapter._vectorTileLayers['esri-standalone'].visible).toBe(true)
})

it('does nothing for an unknown dataset', async () => {
await expect(adapter.applyDatasetVisibility('unknown')).resolves.not.toThrow()
})
})

// ─── applyDatasetOpacity ─────────────────────────────────────────────────────

describe('applyDatasetOpacity', () => {
it('sets opacity on the opacity layer for a known dataset', async () => {
await adapter._addLayers(datasetRegistry.getDataset('esri-standalone'), MAP_STYLE)
await adapter.applyDatasetOpacity('esri-standalone')
expect(adapter._vectorTileOpacityLayers['esri-standalone'].opacity)
.toBe(datasetRegistry.getDataset('esri-standalone').opacity)
})

it('does nothing when the dataset has no opacity layer', async () => {
await expect(adapter.applyDatasetOpacity('esri-standalone')).resolves.not.toThrow()
})

it('does nothing for an unknown dataset', async () => {
await adapter._addLayers(datasetRegistry.getDataset('esri-standalone'), MAP_STYLE)
await expect(adapter.applyDatasetOpacity('unknown')).resolves.not.toThrow()
})
})

// ─── applyGlobalOpacity ──────────────────────────────────────────────────────

describe('applyGlobalOpacity', () => {
it('sets opacity on all opacity layers', async () => {
await adapter._addLayers(datasetRegistry.getDataset('esri-standalone'), MAP_STYLE)
await adapter.applyGlobalOpacity()
expect(adapter._vectorTileOpacityLayers['esri-standalone'].opacity)
.toBe(datasetRegistry.getDataset('esri-standalone').opacity)
})

it('skips entries whose dataset is not in the registry', async () => {
adapter._vectorTileOpacityLayers['ghost-id'] = { opacity: 99 }
await expect(adapter.applyGlobalOpacity()).resolves.not.toThrow()
expect(adapter._vectorTileOpacityLayers['ghost-id'].opacity).toBe(99)
})
})

// ─── onMapStyleChange ────────────────────────────────────────────────────────

describe('onMapStyleChange', () => {
beforeEach(async () => {
await adapter._addLayers(datasetRegistry.getDataset('esri-standalone'), MAP_STYLE)
await adapter._addLayers(datasetRegistry.getDataset('esri-server'), MAP_STYLE)
})

it('calls setStyleLayerVisibility for datasets with esriStyleLayerId', async () => {
await adapter.onMapStyleChange(MAP_STYLE, null)
expect(adapter._vectorTileLayers['esri-standalone'].setStyleLayerVisibility)
.toHaveBeenCalledWith('standalone-style', expect.any(String))
})

it('calls setPaintProperties for datasets not using server style', async () => {
await adapter.onMapStyleChange(MAP_STYLE, null)
expect(adapter._vectorTileLayers['esri-standalone'].setPaintProperties)
.toHaveBeenCalledWith('standalone-style', expect.any(Object))
})

it('does not call setPaintProperties when useServerStyle is true', async () => {
await adapter.onMapStyleChange(MAP_STYLE, null)
expect(adapter._vectorTileLayers['esri-server'].setPaintProperties).not.toHaveBeenCalled()
})

// it('does not call setPaintProperties when getPaintProperties returns null', async () => {
// const vtl = adapter._vectorTileLayers['esri-standalone']
// vtl.getPaintProperties.mockReturnValueOnce(null)
// await adapter.onMapStyleChange(MAP_STYLE, null)
// expect(vtl.setPaintProperties).not.toHaveBeenCalled()
// })
})

// ─── init ────────────────────────────────────────────────────────────────────

describe('init', () => {
beforeEach(async () => {
await adapter.init()
})

it('adds VectorTileLayers for all top-level datasets', async () => {
expect(adapter._vectorTileLayers['flood-zones-cc']).toBeDefined()
expect(adapter._vectorTileLayers['flood-zones']).toBeDefined()
})

it('applies dataset visibility after adding layers', async () => {
expect(adapter._groupLayers['flood-zones-group'].visible).toBe(true)
expect(adapter._vectorTileLayers['flood-zones-cc'].visible).toBe(true)
expect(adapter._vectorTileLayers['flood-zones'].visible).toBe(false)
})

it('adds GroupLayers for datasets with esriGroupId', async () => {
expect(map.add).toHaveBeenCalledWith(expect.objectContaining({ id: 'flood-zones-group' }))
})

it('calls map.add for each top-level dataset', async () => {
expect(map.add.mock.calls.length).toEqual(5) // 3 top-level datasets + 2 group layers

Check warning on line 169 in plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.test.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer "expect(map.add.mock.calls).toHaveLength(5)" over this generic assertion for better reporting; it works on any object with a numeric length property.

See more on https://sonarcloud.io/project/issues?id=DEFRA_interactive-map&issues=AZ9Cc6jkw_HdArncfmzl&open=AZ9Cc6jkw_HdArncfmzl&pullRequest=393
})
})

// ─── onMapSizeChange ─────────────────────────────────────────────────────────

describe('onMapSizeChange', () => {
it('resolves without doing anything', async () => {
await expect(adapter.onMapSizeChange()).resolves.toBeUndefined()
})
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { EsriDataset } from './esriDataset.js'
import { datasetRegistry } from '../../../registry/datasetRegistry.js'

jest.mock('../../../registry/datasetRegistry.js')

const MAP_STYLE = { id: 'outdoor' }

describe('EsriDataset', () => {
beforeEach(() => {
datasetRegistry.attachMapStyle(MAP_STYLE)
datasetRegistry.attachCreateDataset(def => new EsriDataset(def))
datasetRegistry.mockExtend({
// applyLayerPaintProperties
'esri-bare': { id: 'esri-bare' },
// esriGroupId — parent owns the id, child inherits it
'esri-group': { id: 'esri-group', esriGroupId: 'group-123', sublayerIds: ['esri-child'] },
'esri-child': { id: 'esri-child', parentId: 'esri-group' },
// useServerStyle
'esri-server-style': { id: 'esri-server-style', esriUseServerStyle: true }
})
})

// ─── applyLayerPaintProperties ──────────────────────────────────────────────

describe('applyLayerPaintProperties', () => {
it('adds both line-color and fill-color when the dataset has stroke and fill', () => {
const ds = datasetRegistry.getDataset('land-covers-other')
const paint = {}
ds.applyLayerPaintProperties(paint)
expect(paint['line-color']).toBe('#1565C0')
expect(paint['fill-color']).toBe('rgba(0,0,255,0.1)')
})

it('does not add line-color or fill-color when the dataset has neither', () => {
const ds = datasetRegistry.getDataset('esri-bare')
const paint = {}
ds.applyLayerPaintProperties(paint)
expect(paint['line-color']).toBeUndefined()
expect(paint['fill-color']).toBeUndefined()
})
})

// ─── esriGroupId ────────────────────────────────────────────────────────────

describe('esriGroupId', () => {
it('returns the esriGroupId from the dataset definition when set', () => {
const ds = datasetRegistry.getDataset('esri-group')
expect(ds.esriGroupId).toBe('group-123')
})

it('inherits esriGroupId from the parent when own esriGroupId is undefined', () => {
const child = datasetRegistry.getDataset('esri-child')
expect(child.esriGroupId).toBe('group-123')
})

it('returns undefined when esriGroupId is not set and there is no parent', () => {
const ds = datasetRegistry.getDataset('esri-bare')
expect(ds.esriGroupId).toBeUndefined()
})
})

// ─── useServerStyle ─────────────────────────────────────────────────────────

describe('useServerStyle', () => {
it('returns true when esriUseServerStyle is true', () => {
expect(datasetRegistry.getDataset('esri-server-style').useServerStyle).toBe(true)
})

it('returns false when esriUseServerStyle is not set', () => {
expect(datasetRegistry.getDataset('land-covers-other').useServerStyle).toBe(false)
})
})
})
9 changes: 9 additions & 0 deletions plugins/beta/datasets/src/adapters/layerAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export class LayerAdapter {
attachDynamicSources (dynamicSources) {
this._dynamicSources = dynamicSources
}

get dynamicSources () {
return this._dynamicSources
}
}
Loading
Loading