parent
2e4c9a169d
commit
af60da210a
|
@ -27,6 +27,7 @@ export type LayerDescriptor = {
|
|||
__isPreviewLayer?: boolean;
|
||||
__errorMessage?: string;
|
||||
__trackedLayerDescriptor?: LayerDescriptor;
|
||||
__areTilesLoaded?: boolean;
|
||||
alpha?: number;
|
||||
id: string;
|
||||
joins?: JoinDescriptor[];
|
||||
|
|
|
@ -539,3 +539,12 @@ export function setHiddenLayers(hiddenLayerIds: string[]) {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function setAreTilesLoaded(layerId: string, areTilesLoaded: boolean) {
|
||||
return {
|
||||
type: UPDATE_LAYER_PROP,
|
||||
id: layerId,
|
||||
propName: '__areTilesLoaded',
|
||||
newValue: areTilesLoaded,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -400,7 +400,11 @@ export class AbstractLayer implements ILayer {
|
|||
}
|
||||
|
||||
isLayerLoading(): boolean {
|
||||
return this._dataRequests.some((dataRequest) => dataRequest.isLoading());
|
||||
const areTilesLoading =
|
||||
typeof this._descriptor.__areTilesLoaded !== 'undefined'
|
||||
? !this._descriptor.__areTilesLoaded
|
||||
: false;
|
||||
return areTilesLoading || this._dataRequests.some((dataRequest) => dataRequest.isLoading());
|
||||
}
|
||||
|
||||
isLoadingBounds() {
|
||||
|
|
|
@ -16,5 +16,6 @@ interface ITileLayerArguments {
|
|||
|
||||
export class TileLayer extends AbstractLayer {
|
||||
static type: string;
|
||||
|
||||
constructor(args: ITileLayerArguments);
|
||||
}
|
||||
|
|
|
@ -117,8 +117,4 @@ export class TileLayer extends AbstractLayer {
|
|||
getLayerTypeIconName() {
|
||||
return 'grid';
|
||||
}
|
||||
|
||||
isLayerLoading() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
clearGoto,
|
||||
setMapInitError,
|
||||
MapExtentState,
|
||||
setAreTilesLoaded,
|
||||
} from '../../actions';
|
||||
import {
|
||||
getLayerList,
|
||||
|
@ -69,6 +70,9 @@ function mapDispatchToProps(dispatch: ThunkDispatch<MapStoreState, void, AnyActi
|
|||
setMapInitError(errorMessage: string) {
|
||||
dispatch(setMapInitError(errorMessage));
|
||||
},
|
||||
setAreTilesLoaded(layerId: string, areTilesLoaded: boolean) {
|
||||
dispatch(setAreTilesLoaded(layerId, areTilesLoaded));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ import { ResizeChecker } from '../../../../../../src/plugins/kibana_utils/public
|
|||
import { GeoFieldWithIndex } from '../../components/geo_field_with_index';
|
||||
import { RenderToolTipContent } from '../../classes/tooltips/tooltip_property';
|
||||
import { MapExtentState } from '../../actions';
|
||||
import { TileStatusTracker } from './tile_status_tracker';
|
||||
// @ts-expect-error
|
||||
import mbRtlPlugin from '!!file-loader!@mapbox/mapbox-gl-rtl-text/mapbox-gl-rtl-text.min.js';
|
||||
// @ts-expect-error
|
||||
|
@ -72,6 +73,7 @@ export interface Props {
|
|||
onSingleValueTrigger?: (actionId: string, key: string, value: RawValue) => void;
|
||||
geoFields: GeoFieldWithIndex[];
|
||||
renderTooltipContent?: RenderToolTipContent;
|
||||
setAreTilesLoaded: (layerId: string, areTilesLoaded: boolean) => void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
@ -86,6 +88,7 @@ export class MBMap extends Component<Props, State> {
|
|||
private _containerRef: HTMLDivElement | null = null;
|
||||
private _prevDisableInteractive?: boolean;
|
||||
private _navigationControl = new mapboxgl.NavigationControl({ showCompass: false });
|
||||
private _tileStatusTracker?: TileStatusTracker;
|
||||
|
||||
state: State = {
|
||||
prevLayerList: undefined,
|
||||
|
@ -123,6 +126,9 @@ export class MBMap extends Component<Props, State> {
|
|||
if (this._checker) {
|
||||
this._checker.destroy();
|
||||
}
|
||||
if (this._tileStatusTracker) {
|
||||
this._tileStatusTracker.destroy();
|
||||
}
|
||||
if (this.state.mbMap) {
|
||||
this.state.mbMap.remove();
|
||||
this.state.mbMap = undefined;
|
||||
|
@ -199,6 +205,12 @@ export class MBMap extends Component<Props, State> {
|
|||
mbMap.dragRotate.disable();
|
||||
mbMap.touchZoomRotate.disableRotation();
|
||||
|
||||
this._tileStatusTracker = new TileStatusTracker({
|
||||
mbMap,
|
||||
getCurrentLayerList: () => this.props.layerList,
|
||||
setAreTilesLoaded: this.props.setAreTilesLoaded,
|
||||
});
|
||||
|
||||
const tooManyFeaturesImageSrc =
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAA7DgAAOw4BzLahgwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAARLSURBVHic7ZnPbxRVAMe/7735sWO3293ZlUItJsivCxEE0oTYRgu1FqTQoFSwKTYx8SAH/wHjj4vRozGGi56sMcW2UfqTEuOhppE0KJc2GIuKQFDY7qzdtrudX88D3YTUdFuQN8+k87ltZt7uZz958/bNLAGwBWsYKltANmEA2QKyCQPIFpBNGEC2gGzCALIFZBMGkC0gmzCAbAHZhAFkC8gmDCBbQDZhANkCslnzARQZH6oDpNs0D5UDSUIInePcOpPLfdfnODNBuwQWIAWwNOABwHZN0x8npE6hNLJ4DPWRyFSf40wE5VOEQPBjcR0g3YlE4ybGmtK+/1NzJtOZA/xSYwZMs3nG962T2ez3It2AANaA/kSidYuivOQBs5WM1fUnk6f0u+GXJUqIuUtVXx00zRbRfkIDfBqL7a1WlIYbjvNtTTr99jXXHVpH6dMjK0R4cXq6c9rzxjcx9sKX8XitSEdhAToMI7VP10/97fsTh7PZrgWAN1lW72KE2vOm2b5chDTgtWQyn93x/bEEIetEOQIC14CxVOr1CkKefH929t0v8vn0vcdGEoljGxXl4C3PGz2YyXy+AHARDqtByAxoUdWKBKV70r4/vvTLA0CjZfX+5nkDGxirKzUTgkBIgNaysh3gnF627R+XO+dQJvP1ddcdrmSsbtA020pF+CAW21qrqmUiXIUEqGRsIwD0FQq/lzqv0bJ6rrvucBVjzwyb5ivLRTiiaW+8VV7eIEBVTAANiIIQd9RxZlc6t9Gyem647vn1jD07ZJonl4sQASoevqmgABzwwHnJzc69PGdZ3X+47sgGxuqHTPPE0ggeVtg5/QeEBMhxPg1Aa1DV2GrHPG9ZXy1G2D+wNALn9jyQEeHKAJgP+033Kgrdqij7AFwZtu3bqx3XWShMHtV1o1pRGo4YxiNd+fyEB2DKdX/4aG5u0hbwcylkBryTy/3scT6zW9Nq7ndso2Wdvea6Q1WUHuiPx1/WAXLBcWZXun94UMRcAoD/p+ddTFK6u8MwUvc7vsmyem+67oVqVT0wkEgcF+FYRNhW+L25uX6f84XThtHxIBudE5bVY/t++jFVrU/dvVSFICzAqG3PX/S8rihj2/61qK1AOUB7ksl2jdLUL7Z9rvgcQQRCFsEi5wqFmw26XnhCUQ63GcZmCly95Lrzpca0G0byk3j8tEnpU1c975tmyxoU5QcE8EAEAM5WVOzfoarHAeC2749dcpzxMwsLv07Ztg0AOzVNf03Ttu/S9T2PMlbjc25fdpyutmx2TLRbIAEA4M1otKo1EjmaoHQn4ZwBgA/kAVAK6MXXdzxv/ONcrq/HcbJBeAUWoEizqsaORaPbKglZrxMSZZyrM76f/ovzWx/m85PFWREUgQf4v7Hm/xcIA8gWkE0YQLaAbMIAsgVkEwaQLSCbMIBsAdmEAWQLyCYMIFtANmEA2QKyCQPIFpDNmg/wD3OFdEybUvJjAAAAAElFTkSuQmCC';
|
||||
const tooManyFeaturesImage = new Image();
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line max-classes-per-file
|
||||
import { TileStatusTracker } from './tile_status_tracker';
|
||||
import { Map as MbMap } from 'mapbox-gl';
|
||||
import { ILayer } from '../../classes/layers/layer';
|
||||
|
||||
class MockMbMap {
|
||||
public listeners: Array<{ type: string; callback: (e: unknown) => void }> = [];
|
||||
|
||||
on(type: string, callback: (e: unknown) => void) {
|
||||
this.listeners.push({
|
||||
type,
|
||||
callback,
|
||||
});
|
||||
}
|
||||
|
||||
emit(type: string, e: unknown) {
|
||||
this.listeners.forEach((listener) => {
|
||||
if (listener.type === type) {
|
||||
listener.callback(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
off(type: string, callback: (e: unknown) => void) {
|
||||
this.listeners = this.listeners.filter((listener) => {
|
||||
return !(listener.type === type && listener.callback === callback);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class MockLayer {
|
||||
readonly _id: string;
|
||||
readonly _mbSourceId: string;
|
||||
constructor(id: string, mbSourceId: string) {
|
||||
this._id = id;
|
||||
this._mbSourceId = mbSourceId;
|
||||
}
|
||||
getId() {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
ownsMbSourceId(mbSourceId: string) {
|
||||
return this._mbSourceId === mbSourceId;
|
||||
}
|
||||
}
|
||||
|
||||
function createMockLayer(id: string, mbSourceId: string): ILayer {
|
||||
return (new MockLayer(id, mbSourceId) as unknown) as ILayer;
|
||||
}
|
||||
|
||||
function createMockMbDataEvent(mbSourceId: string, tileKey: string): unknown {
|
||||
return {
|
||||
sourceId: mbSourceId,
|
||||
dataType: 'source',
|
||||
tile: {
|
||||
tileID: {
|
||||
key: tileKey,
|
||||
},
|
||||
},
|
||||
source: {
|
||||
type: 'vector',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function sleep(timeout: number) {
|
||||
return await new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(true);
|
||||
}, timeout);
|
||||
});
|
||||
}
|
||||
|
||||
describe('TileStatusTracker', () => {
|
||||
test('should add and remove tiles', async () => {
|
||||
const mockMbMap = new MockMbMap();
|
||||
const loadedMap: Map<string, boolean> = new Map<string, boolean>();
|
||||
new TileStatusTracker({
|
||||
mbMap: (mockMbMap as unknown) as MbMap,
|
||||
setAreTilesLoaded: (layerId, areTilesLoaded) => {
|
||||
loadedMap.set(layerId, areTilesLoaded);
|
||||
},
|
||||
getCurrentLayerList: () => {
|
||||
return [
|
||||
createMockLayer('foo', 'foosource'),
|
||||
createMockLayer('bar', 'barsource'),
|
||||
createMockLayer('foobar', 'foobarsource'),
|
||||
];
|
||||
},
|
||||
});
|
||||
|
||||
mockMbMap.emit('sourcedataloading', createMockMbDataEvent('foosource', 'aa11'));
|
||||
|
||||
const aa11BarTile = createMockMbDataEvent('barsource', 'aa11');
|
||||
mockMbMap.emit('sourcedataloading', aa11BarTile);
|
||||
|
||||
mockMbMap.emit('sourcedata', createMockMbDataEvent('foosource', 'aa11'));
|
||||
|
||||
// simulate delay. Cache-checking is debounced.
|
||||
await sleep(300);
|
||||
|
||||
expect(loadedMap.get('foo')).toBe(true);
|
||||
expect(loadedMap.get('bar')).toBe(false); // still outstanding tile requests
|
||||
expect(loadedMap.has('foobar')).toBe(true); // never received tile requests
|
||||
|
||||
(aa11BarTile as { tile: { aborted: boolean } })!.tile.aborted = true; // abort tile
|
||||
mockMbMap.emit('sourcedataloading', createMockMbDataEvent('barsource', 'af1d'));
|
||||
mockMbMap.emit('sourcedataloading', createMockMbDataEvent('foosource', 'af1d'));
|
||||
mockMbMap.emit('error', createMockMbDataEvent('barsource', 'af1d'));
|
||||
|
||||
// simulate delay. Cache-checking is debounced.
|
||||
await sleep(300);
|
||||
|
||||
expect(loadedMap.get('foo')).toBe(false); // still outstanding tile requests
|
||||
expect(loadedMap.get('bar')).toBe(true); // tiles were aborted or errored
|
||||
expect(loadedMap.has('foobar')).toBe(true); // never received tile requests
|
||||
});
|
||||
|
||||
test('should cleanup listeners on destroy', async () => {
|
||||
const mockMbMap = new MockMbMap();
|
||||
const tileStatusTracker = new TileStatusTracker({
|
||||
mbMap: (mockMbMap as unknown) as MbMap,
|
||||
setAreTilesLoaded: () => {},
|
||||
getCurrentLayerList: () => {
|
||||
return [];
|
||||
},
|
||||
});
|
||||
|
||||
expect(mockMbMap.listeners.length).toBe(3);
|
||||
tileStatusTracker.destroy();
|
||||
expect(mockMbMap.listeners.length).toBe(0);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Map as MapboxMap, MapSourceDataEvent } from 'mapbox-gl';
|
||||
import _ from 'lodash';
|
||||
import { ILayer } from '../../classes/layers/layer';
|
||||
import { SPATIAL_FILTERS_LAYER_ID } from '../../../common/constants';
|
||||
|
||||
interface MbTile {
|
||||
// references internal object from mapbox
|
||||
aborted?: boolean;
|
||||
}
|
||||
|
||||
interface Tile {
|
||||
mbKey: string;
|
||||
mbSourceId: string;
|
||||
mbTile: MbTile;
|
||||
}
|
||||
|
||||
export class TileStatusTracker {
|
||||
private _tileCache: Tile[];
|
||||
private readonly _mbMap: MapboxMap;
|
||||
private readonly _setAreTilesLoaded: (layerId: string, areTilesLoaded: boolean) => void;
|
||||
private readonly _getCurrentLayerList: () => ILayer[];
|
||||
private readonly _onSourceDataLoading = (e: MapSourceDataEvent) => {
|
||||
if (
|
||||
e.sourceId &&
|
||||
e.sourceId !== SPATIAL_FILTERS_LAYER_ID &&
|
||||
e.dataType === 'source' &&
|
||||
e.tile &&
|
||||
(e.source.type === 'vector' || e.source.type === 'raster')
|
||||
) {
|
||||
const tracked = this._tileCache.find((tile) => {
|
||||
return (
|
||||
tile.mbKey === ((e.tile.tileID.key as unknown) as string) &&
|
||||
tile.mbSourceId === e.sourceId
|
||||
);
|
||||
});
|
||||
|
||||
if (!tracked) {
|
||||
this._tileCache.push({
|
||||
mbKey: (e.tile.tileID.key as unknown) as string,
|
||||
mbSourceId: e.sourceId,
|
||||
mbTile: e.tile,
|
||||
});
|
||||
this._updateTileStatus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private readonly _onError = (e: MapSourceDataEvent) => {
|
||||
if (
|
||||
e.sourceId &&
|
||||
e.sourceId !== SPATIAL_FILTERS_LAYER_ID &&
|
||||
e.tile &&
|
||||
(e.source.type === 'vector' || e.source.type === 'raster')
|
||||
) {
|
||||
this._removeTileFromCache(e.sourceId, (e.tile.tileID.key as unknown) as string);
|
||||
}
|
||||
};
|
||||
private readonly _onSourceData = (e: MapSourceDataEvent) => {
|
||||
if (
|
||||
e.sourceId &&
|
||||
e.sourceId !== SPATIAL_FILTERS_LAYER_ID &&
|
||||
e.dataType === 'source' &&
|
||||
e.tile &&
|
||||
(e.source.type === 'vector' || e.source.type === 'raster')
|
||||
) {
|
||||
this._removeTileFromCache(e.sourceId, (e.tile.tileID.key as unknown) as string);
|
||||
}
|
||||
};
|
||||
|
||||
constructor({
|
||||
mbMap,
|
||||
setAreTilesLoaded,
|
||||
getCurrentLayerList,
|
||||
}: {
|
||||
mbMap: MapboxMap;
|
||||
setAreTilesLoaded: (layerId: string, areTilesLoaded: boolean) => void;
|
||||
getCurrentLayerList: () => ILayer[];
|
||||
}) {
|
||||
this._tileCache = [];
|
||||
this._setAreTilesLoaded = setAreTilesLoaded;
|
||||
this._getCurrentLayerList = getCurrentLayerList;
|
||||
|
||||
this._mbMap = mbMap;
|
||||
this._mbMap.on('sourcedataloading', this._onSourceDataLoading);
|
||||
this._mbMap.on('error', this._onError);
|
||||
this._mbMap.on('sourcedata', this._onSourceData);
|
||||
}
|
||||
|
||||
_updateTileStatus = _.debounce(() => {
|
||||
this._tileCache = this._tileCache.filter((tile) => {
|
||||
return typeof tile.mbTile.aborted === 'boolean' ? !tile.mbTile.aborted : true;
|
||||
});
|
||||
const layerList = this._getCurrentLayerList();
|
||||
for (let i = 0; i < layerList.length; i++) {
|
||||
const layer: ILayer = layerList[i];
|
||||
let atLeastOnePendingTile = false;
|
||||
for (let j = 0; j < this._tileCache.length; j++) {
|
||||
const tile = this._tileCache[j];
|
||||
if (layer.ownsMbSourceId(tile.mbSourceId)) {
|
||||
atLeastOnePendingTile = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this._setAreTilesLoaded(layer.getId(), !atLeastOnePendingTile);
|
||||
}
|
||||
}, 100);
|
||||
|
||||
_removeTileFromCache = (mbSourceId: string, mbKey: string) => {
|
||||
const trackedIndex = this._tileCache.findIndex((tile) => {
|
||||
return tile.mbKey === ((mbKey as unknown) as string) && tile.mbSourceId === mbSourceId;
|
||||
});
|
||||
|
||||
if (trackedIndex >= 0) {
|
||||
this._tileCache.splice(trackedIndex, 1);
|
||||
this._updateTileStatus();
|
||||
}
|
||||
};
|
||||
|
||||
destroy() {
|
||||
this._mbMap.off('error', this._onError);
|
||||
this._mbMap.off('sourcedata', this._onSourceData);
|
||||
this._mbMap.off('sourcedataloading', this._onSourceDataLoading);
|
||||
this._tileCache.length = 0;
|
||||
}
|
||||
}
|
|
@ -109,7 +109,23 @@ exports[`LayerControl isLayerTOCOpen Should render expand button with error icon
|
|||
</EuiToolTip>
|
||||
`;
|
||||
|
||||
exports[`LayerControl isLayerTOCOpen Should render expand button with loading icon when layer is loading 1`] = `
|
||||
exports[`LayerControl isLayerTOCOpen spinner icon Should not render expand button with loading icon when layer is invisible 1`] = `
|
||||
<EuiToolTip
|
||||
content="Expand layers panel"
|
||||
delay="long"
|
||||
position="left"
|
||||
>
|
||||
<EuiButtonIcon
|
||||
aria-label="Expand layers panel"
|
||||
className="mapLayerControl__openLayerTOCButton"
|
||||
color="text"
|
||||
iconType="menuLeft"
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
`;
|
||||
|
||||
exports[`LayerControl isLayerTOCOpen spinner icon Should render expand button with loading icon when layer is loading 1`] = `
|
||||
<EuiToolTip
|
||||
content="Expand layers panel"
|
||||
delay="long"
|
||||
|
|
|
@ -83,13 +83,13 @@ export class TOCEntryButton extends Component<Props, State> {
|
|||
/>
|
||||
);
|
||||
tooltipContent = this.props.layer.getErrors();
|
||||
} else if (this.props.layer.isLayerLoading()) {
|
||||
icon = <EuiLoadingSpinner size="m" />;
|
||||
} else if (!this.props.layer.isVisible()) {
|
||||
icon = <EuiIcon size="m" type="eyeClosed" />;
|
||||
tooltipContent = i18n.translate('xpack.maps.layer.layerHiddenTooltip', {
|
||||
defaultMessage: `Layer is hidden.`,
|
||||
});
|
||||
} else if (this.props.layer.isLayerLoading()) {
|
||||
icon = <EuiLoadingSpinner size="m" />;
|
||||
} else if (!this.props.layer.showAtZoomLevel(this.props.zoom)) {
|
||||
const minZoom = this.props.layer.getMinZoom();
|
||||
const maxZoom = this.props.layer.getMaxZoom();
|
||||
|
|
|
@ -65,7 +65,7 @@ export function LayerControl({
|
|||
return layer.hasErrors();
|
||||
});
|
||||
const isLoading = layerList.some((layer) => {
|
||||
return layer.isLayerLoading();
|
||||
return layer.isLayerLoading() && layer.isVisible();
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
|
@ -47,28 +47,44 @@ describe('LayerControl', () => {
|
|||
describe('isLayerTOCOpen', () => {
|
||||
test('Should render expand button', () => {
|
||||
const component = shallow(<LayerControl {...defaultProps} isLayerTOCOpen={false} />);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('Should render expand button with loading icon when layer is loading', () => {
|
||||
describe('spinner icon', () => {
|
||||
const isLayerLoading = true;
|
||||
let isVisible = true;
|
||||
const mockLayerThatIsLoading = {
|
||||
hasErrors: () => {
|
||||
return false;
|
||||
},
|
||||
isLayerLoading: () => {
|
||||
return true;
|
||||
return isLayerLoading;
|
||||
},
|
||||
isVisible: () => {
|
||||
return isVisible;
|
||||
},
|
||||
};
|
||||
const component = shallow(
|
||||
<LayerControl
|
||||
{...defaultProps}
|
||||
isLayerTOCOpen={false}
|
||||
layerList={[mockLayerThatIsLoading]}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
test('Should render expand button with loading icon when layer is loading', () => {
|
||||
const component = shallow(
|
||||
<LayerControl
|
||||
{...defaultProps}
|
||||
isLayerTOCOpen={false}
|
||||
layerList={[mockLayerThatIsLoading]}
|
||||
/>
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
test('Should not render expand button with loading icon when layer is invisible', () => {
|
||||
isVisible = false;
|
||||
const component = shallow(
|
||||
<LayerControl
|
||||
{...defaultProps}
|
||||
isLayerTOCOpen={false}
|
||||
layerList={[mockLayerThatIsLoading]}
|
||||
/>
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
test('Should render expand button with error icon when layer has error', () => {
|
||||
|
|
Loading…
Reference in a new issue