import Collection from '../../../../../src/ol/Collection.js'; import Layer from '../../../../../src/ol/layer/Layer.js'; import LayerGroup from '../../../../../src/ol/layer/Group.js'; import Source from '../../../../../src/ol/source/Source.js'; import {assign} from '../../../../../src/ol/obj.js'; import {getIntersection} from '../../../../../src/ol/extent.js'; import {getUid} from '../../../../../src/ol/util.js'; describe('ol/layer/Group', function () { function disposeHierarchy(layer) { if (layer instanceof LayerGroup) { layer.getLayers().forEach((l) => disposeHierarchy(l)); } layer.dispose(); } describe('constructor (defaults)', function () { /** @type {LayerGroup} */ let group; beforeEach(function () { group = new LayerGroup(); }); afterEach(function () { group.dispose(); }); it('creates an instance', function () { expect(group).to.be.a(LayerGroup); }); it('provides default opacity', function () { expect(group.getOpacity()).to.be(1); }); it('provides default visibility', function () { expect(group.getVisible()).to.be(true); }); it('provides default layerState', function () { expect(group.getLayerState()).to.eql({ layer: group, opacity: 1, visible: true, managed: true, extent: undefined, zIndex: undefined, maxResolution: Infinity, minResolution: 0, minZoom: -Infinity, maxZoom: Infinity, }); }); it('provides default empty layers collection', function () { expect(group.getLayers()).to.be.a(Collection); expect(group.getLayers().getLength()).to.be(0); }); }); describe('generic change event', function () { let layer, group, listener; beforeEach(function () { layer = new Layer({ source: new Source({ projection: 'EPSG:4326', }), }); group = new LayerGroup({ layers: [layer], }); listener = sinon.spy(); }); afterEach(function () { disposeHierarchy(group); }); it('is dispatched by the group when layer opacity changes', function () { group.on('change', listener); layer.setOpacity(0.5); expect(listener.calledOnce).to.be(true); }); it('is dispatched by the group when layer visibility changes', function () { group.on('change', listener); layer.setVisible(false); expect(listener.callCount).to.be(1); layer.setVisible(true); expect(listener.callCount).to.be(2); }); }); describe('property change event', function () { let layer, group, listener; beforeEach(function () { layer = new Layer({ source: new Source({ projection: 'EPSG:4326', }), }); group = new LayerGroup({ layers: [layer], }); listener = sinon.spy(); }); afterEach(function () { disposeHierarchy(group); }); it('is dispatched by the group when group opacity changes', function () { group.on('propertychange', listener); group.setOpacity(0.5); expect(listener.calledOnce).to.be(true); }); it('is dispatched by the group when group visibility changes', function () { group.on('propertychange', listener); group.setVisible(false); expect(listener.callCount).to.be(1); group.setVisible(true); expect(listener.callCount).to.be(2); }); }); describe('constructor (options)', function () { it('accepts options', function () { const layer = new Layer({ source: new Source({ projection: 'EPSG:4326', }), }); const group = new LayerGroup({ layers: [layer], opacity: 0.5, visible: false, zIndex: 10, maxResolution: 500, minResolution: 0.25, minZoom: 1, maxZoom: 10, }); expect(group.getOpacity()).to.be(0.5); expect(group.getVisible()).to.be(false); expect(group.getMaxResolution()).to.be(500); expect(group.getMinResolution()).to.be(0.25); expect(group.getMinZoom()).to.be(1); expect(group.getMaxZoom()).to.be(10); expect(group.getLayerState()).to.eql({ layer: group, opacity: 0.5, visible: false, managed: true, extent: undefined, zIndex: 10, maxResolution: 500, minResolution: 0.25, minZoom: 1, maxZoom: 10, }); expect(group.getLayers()).to.be.a(Collection); expect(group.getLayers().getLength()).to.be(1); expect(group.getLayers().item(0)).to.be(layer); disposeHierarchy(group); }); it('accepts an extent option', function () { const layer = new Layer({ source: new Source({ projection: 'EPSG:4326', }), }); const groupExtent = [-10, -5, 10, 5]; const group = new LayerGroup({ layers: [layer], opacity: 0.5, visible: false, extent: groupExtent, maxResolution: 500, minResolution: 0.25, }); expect(group.getOpacity()).to.be(0.5); expect(group.getVisible()).to.be(false); expect(group.getExtent()).to.eql(groupExtent); expect(group.getMaxResolution()).to.be(500); expect(group.getMinResolution()).to.be(0.25); expect(group.getLayerState()).to.eql({ layer: group, opacity: 0.5, visible: false, managed: true, extent: groupExtent, zIndex: undefined, maxResolution: 500, minResolution: 0.25, minZoom: -Infinity, maxZoom: Infinity, }); expect(group.getLayers()).to.be.a(Collection); expect(group.getLayers().getLength()).to.be(1); expect(group.getLayers().item(0)).to.be(layer); disposeHierarchy(group); }); }); describe('addlayer event', () => { it('is dispatched when a layer is added', (done) => { const group = new LayerGroup(); const layer = new Layer({}); group.on('addlayer', (event) => { expect(event.layer).to.be(layer); done(); }); group.getLayers().push(layer); }); it('is dispatched once for each layer added', (done) => { const group = new LayerGroup(); const layers = [new Layer({}), new Layer({}), new Layer({})]; let count = 0; group.on('addlayer', (event) => { expect(event.layer).to.be(layers[count]); count++; if (count === layers.length) { done(); } }); group.getLayers().extend(layers); }); it('is dispatched when setLayers is called', (done) => { const group = new LayerGroup(); const layers = [new Layer({}), new Layer({}), new Layer({})]; let count = 0; group.on('addlayer', (event) => { expect(event.layer).to.be(layers[count]); count++; if (count === layers.length) { done(); } }); group.setLayers(new Collection(layers)); }); it('is dispatched when a layer group is added', (done) => { const group = new LayerGroup(); const layer = new LayerGroup(); group.on('addlayer', (event) => { expect(event.layer).to.be(layer); done(); }); group.getLayers().push(layer); }); it('is dispatched for each layer added to a child group', (done) => { const group = new LayerGroup(); const child = new LayerGroup(); group.getLayers().push(child); const layer = new Layer({}); group.on('addlayer', (event) => { expect(event.layer).to.be(layer); done(); }); child.getLayers().push(layer); }); it('is dispatched for each layer added to a child group configured at construction', (done) => { const child = new LayerGroup(); const group = new LayerGroup({ layers: [child], }); const layer = new Layer({}); group.on('addlayer', (event) => { expect(event.layer).to.be(layer); done(); }); child.getLayers().push(layer); }); it('is not dispatched for layers added to a child group after the child group is removed', (done) => { const child = new LayerGroup(); const group = new LayerGroup({ layers: [child], }); const layer = new Layer({}); group.on('addlayer', (event) => { done(new Error('unexpected addlayer after group removal')); }); group.getLayers().remove(child); child.getLayers().push(layer); setTimeout(done, 10); }); }); describe('removelayer event', () => { it('is dispatched when a layer is removed', (done) => { const layer = new Layer({}); const group = new LayerGroup({layers: [layer]}); group.on('removelayer', (event) => { expect(event.layer).to.be(layer); done(); }); group.getLayers().remove(layer); }); it('is dispatched when a setLayers is called', (done) => { const layer = new Layer({}); const group = new LayerGroup({layers: [layer]}); group.on('removelayer', (event) => { expect(event.layer).to.be(layer); done(); }); group.setLayers(new Collection()); }); it('is dispatched when a layer is removed from a child group', (done) => { const layer = new Layer({}); const child = new LayerGroup({layers: [layer]}); const group = new LayerGroup({layers: [child]}); group.on('removelayer', (event) => { expect(event.layer).to.be(layer); done(); }); child.getLayers().remove(layer); }); it('is not dispatched when a layer is removed from a child group after child group removal', (done) => { const layer = new Layer({}); const child = new LayerGroup({layers: [layer]}); const group = new LayerGroup({layers: [child]}); group.getLayers().remove(child); group.on('removelayer', (event) => { done(new Error('unexpected removelayer after group removal')); }); child.getLayers().remove(layer); setTimeout(done, 10); }); }); describe('#getLayerState', function () { let group; beforeEach(function () { group = new LayerGroup(); }); afterEach(function () { disposeHierarchy(group); }); it('returns a layerState from the properties values', function () { group.setOpacity(0.3); group.setVisible(false); group.setZIndex(10); const groupExtent = [-100, 50, 100, 50]; group.setExtent(groupExtent); group.setMaxResolution(500); group.setMinResolution(0.25); group.setMinZoom(5); group.setMaxZoom(10); expect(group.getLayerState()).to.eql({ layer: group, opacity: 0.3, visible: false, managed: true, extent: groupExtent, zIndex: 10, maxResolution: 500, minResolution: 0.25, minZoom: 5, maxZoom: 10, }); }); it('returns a layerState with clamped values', function () { group.setOpacity(-1.5); group.setVisible(false); expect(group.getLayerState()).to.eql({ layer: group, opacity: 0, visible: false, managed: true, extent: undefined, zIndex: undefined, maxResolution: Infinity, minResolution: 0, minZoom: -Infinity, maxZoom: Infinity, }); group.setOpacity(3); group.setVisible(true); expect(group.getLayerState()).to.eql({ layer: group, opacity: 1, visible: true, managed: true, extent: undefined, zIndex: undefined, maxResolution: Infinity, minResolution: 0, minZoom: -Infinity, maxZoom: Infinity, }); }); }); describe('layers events', function () { it('listen / unlisten for layers added to the collection', function () { const layers = new Collection(); const group = new LayerGroup({ layers: layers, }); expect(Object.keys(group.listenerKeys_).length).to.eql(0); const layer = new Layer({}); layers.push(layer); expect(Object.keys(group.listenerKeys_).length).to.eql(1); const listeners = group.listenerKeys_[getUid(layer)]; expect(listeners.length).to.eql(2); expect(typeof listeners[0]).to.be('object'); expect(typeof listeners[1]).to.be('object'); // remove the layer from the group layers.pop(); expect(Object.keys(group.listenerKeys_).length).to.eql(0); expect(listeners[0].listener).to.be(undefined); expect(listeners[1].listener).to.be(undefined); }); }); describe('#setLayers', function () { it('sets layers property', function () { const layer = new Layer({ source: new Source({ projection: 'EPSG:4326', }), }); const layers = new Collection([layer]); const group = new LayerGroup(); group.setLayers(layers); expect(group.getLayers()).to.be(layers); disposeHierarchy(group); }); }); describe('#getLayerStatesArray', function () { let layer1, layer2, layer3; beforeEach(function () { layer1 = new Layer({ source: new Source({ projection: 'EPSG:4326', }), }); layer2 = new Layer({ source: new Source({ projection: 'EPSG:4326', }), opacity: 0.5, visible: false, maxResolution: 500, minResolution: 0.25, }); layer3 = new Layer({ source: new Source({ projection: 'EPSG:4326', }), extent: [-5, -2, 5, 2], }); }); afterEach(function () { layer1.dispose(); layer2.dispose(); layer3.dispose(); }); it('returns an empty array if no layer', function () { const group = new LayerGroup(); const layerStatesArray = group.getLayerStatesArray(); expect(layerStatesArray).to.be.a(Array); expect(layerStatesArray.length).to.be(0); group.dispose(); }); it('does not transform layerStates by default', function () { const group = new LayerGroup({ layers: [layer1, layer2], }); const layerStatesArray = group.getLayerStatesArray(); expect(layerStatesArray).to.be.a(Array); expect(layerStatesArray.length).to.be(2); expect(layerStatesArray[0]).to.eql(layer1.getLayerState()); // layer state should match except for layer reference const layerState = assign({}, layerStatesArray[0]); delete layerState.layer; const groupState = assign({}, group.getLayerState()); delete groupState.layer; expect(layerState).to.eql(groupState); expect(layerStatesArray[1]).to.eql(layer2.getLayerState()); group.dispose(); }); it('uses the layer group extent if layer has no extent', function () { const groupExtent = [-10, -5, 10, 5]; const group = new LayerGroup({ extent: groupExtent, layers: [layer1], }); const layerStatesArray = group.getLayerStatesArray(); expect(layerStatesArray[0].extent).to.eql(groupExtent); group.dispose(); }); it('uses the intersection of group and child extent', function () { const groupExtent = [-10, -5, 10, 5]; const group = new LayerGroup({ extent: groupExtent, layers: [layer3], }); const layerStatesArray = group.getLayerStatesArray(); expect(layerStatesArray[0].extent).to.eql( getIntersection(layer3.getExtent(), groupExtent) ); group.dispose(); }); it('transforms layerStates correctly', function () { const group = new LayerGroup({ layers: [layer1, layer2], opacity: 0.5, visible: false, maxResolution: 150, minResolution: 0.2, }); const layerStatesArray = group.getLayerStatesArray([]); // compare layer state to group state // layer state should match except for layer reference let layerState = assign({}, layerStatesArray[0]); delete layerState.layer; const groupState = assign({}, group.getLayerState()); delete groupState.layer; expect(layerState).to.eql(groupState); // layer state should be transformed (and we ignore layer reference) layerState = assign({}, layerStatesArray[1]); delete layerState.layer; expect(layerState).to.eql({ opacity: 0.25, visible: false, managed: true, extent: undefined, zIndex: undefined, maxResolution: 150, minResolution: 0.25, minZoom: -Infinity, maxZoom: Infinity, }); group.dispose(); }); it('returns max minZoom', function () { const group = new LayerGroup({ minZoom: 5, layers: [ new Layer({ source: new Source({ projection: 'EPSG:4326', }), }), new Layer({ source: new Source({ projection: 'EPSG:4326', }), minZoom: 10, }), ], }); expect(group.getLayerStatesArray()[0].minZoom).to.be(5); expect(group.getLayerStatesArray()[1].minZoom).to.be(10); disposeHierarchy(group); }); it('returns min maxZoom of layers', function () { const group = new LayerGroup({ maxZoom: 5, layers: [ new Layer({ source: new Source({ projection: 'EPSG:4326', }), }), new Layer({ source: new Source({ projection: 'EPSG:4326', }), maxZoom: 2, }), ], }); expect(group.getLayerStatesArray()[0].maxZoom).to.be(5); expect(group.getLayerStatesArray()[1].maxZoom).to.be(2); disposeHierarchy(group); }); it('uses the layer group zIndex if layer has no zIndex', function () { const layerM1 = new Layer({ zIndex: -1, source: new Source({}), }); const layerUndefined = new Layer({ source: new Source({}), }); const layer0 = new Layer({ zIndex: 0, source: new Source({}), }); const group = new LayerGroup({ zIndex: 2, layers: [layerM1, layerUndefined, layer0], }); const layerStatesArray = group.getLayerStatesArray(); expect(layerStatesArray[0].zIndex).to.be(-1); expect(layerStatesArray[1].zIndex).to.be(2); expect(layerStatesArray[2].zIndex).to.be(0); disposeHierarchy(group); }); it('uses the deepest nested group with zIndex as default', function () { const group = new LayerGroup({ zIndex: 1, layers: [ new LayerGroup({ zIndex: 5, layers: [ new Layer({ source: new Source({}), }), ], }), ], }); const layerStatesArray = group.getLayerStatesArray(); expect(layerStatesArray[0].zIndex).to.be(5); disposeHierarchy(group); }); it('uses zIndex of closest parent group where it is not undefined', function () { const group = new LayerGroup({ zIndex: 1, layers: [ new LayerGroup({ layers: [ new Layer({ source: new Source({}), }), ], }), ], }); const layerStatesArray = group.getLayerStatesArray(); expect(layerStatesArray[0].zIndex).to.be(1); disposeHierarchy(group); }); }); });