import Group from '../../../../../src/ol/layer/Group.js'; import Layer, {inView} from '../../../../../src/ol/layer/Layer.js'; import Map from '../../../../../src/ol/Map.js'; import Property from '../../../../../src/ol/layer/Property.js'; import RenderEvent from '../../../../../src/ol/render/Event.js'; import Source from '../../../../../src/ol/source/Source.js'; import {get as getProjection} from '../../../../../src/ol/proj.js'; describe('ol/layer/Layer', function () { describe('constructor (defaults)', function () { let layer; beforeEach(function () { layer = new Layer({ source: new Source({ projection: getProjection('EPSG:4326'), }), }); }); afterEach(function () { layer.dispose(); }); it('creates an instance', function () { expect(layer).to.be.a(Layer); }); it('provides default opacity', function () { expect(layer.getOpacity()).to.be(1); }); it('provides default visibility', function () { expect(layer.getVisible()).to.be(true); }); it('provides default max resolution', function () { expect(layer.getMaxResolution()).to.be(Infinity); }); it('provides default min resolution', function () { expect(layer.getMinResolution()).to.be(0); }); it('provides default min zoom', function () { expect(layer.getMinZoom()).to.be(-Infinity); }); it('provides default max zoom', function () { expect(layer.getMaxZoom()).to.be(Infinity); }); it('provides default layerState', function () { expect(layer.getLayerState()).to.eql({ layer: layer, opacity: 1, visible: true, managed: true, sourceState: 'ready', extent: undefined, zIndex: undefined, maxResolution: Infinity, minResolution: 0, minZoom: -Infinity, maxZoom: Infinity, }); }); }); describe('constructor (options)', function () { it('accepts options', function () { const layer = new Layer({ source: new Source({ projection: getProjection('EPSG:4326'), }), opacity: 0.5, visible: false, zIndex: 10, maxResolution: 500, minResolution: 0.25, minZoom: 1, maxZoom: 10, foo: 42, }); expect(layer.getOpacity()).to.be(0.5); expect(layer.getVisible()).to.be(false); expect(layer.getMaxResolution()).to.be(500); expect(layer.getMinResolution()).to.be(0.25); expect(layer.getMinZoom()).to.be(1); expect(layer.getMaxZoom()).to.be(10); expect(layer.get('foo')).to.be(42); expect(layer.getLayerState()).to.eql({ layer: layer, opacity: 0.5, visible: false, managed: true, sourceState: 'ready', extent: undefined, zIndex: 10, maxResolution: 500, minResolution: 0.25, minZoom: 1, maxZoom: 10, }); layer.dispose(); }); it('assigns key-value pairs of `properties` to the object', function () { const o = new Layer({ properties: { foo: 'bar', }, }); expect(o.get('foo')).to.be('bar'); expect(o.get('properties')).to.be(undefined); }); it('can have a `properties` property', function () { const o = new Layer({ properties: { properties: {foo: 'bar'}, }, }); expect(o.get('properties')).to.eql({foo: 'bar'}); }); it('throws on non-numeric opacity', function () { function create() { new Layer({ source: new Source({ projection: 'EPSG:4326', }), opacity: 'foo', }); } expect(create).to.throwException(); }); it('accepts a custom render function', function () { let called = false; const layer = new Layer({ render: function () { called = true; }, }); layer.render(); expect(called).to.eql(true); }); }); describe('inView', function () { let layer; beforeEach(function () { layer = new Layer({ source: new Source({ projection: getProjection('EPSG:4326'), }), }); }); afterEach(function () { layer.dispose(); }); const cases = [ { when: 'layer is not visible', visible: false, view: { resolution: 4, zoom: 4, }, inView: false, }, { when: 'layer is not visible (with min/max zoom and resolution)', visible: false, minZoom: 2, maxZoom: 6, minResolution: 2, maxResolution: 6, view: { resolution: 4, zoom: 4, }, inView: false, }, { when: 'view zoom is less than minZoom', minZoom: 2, view: { resolution: 1, zoom: 1, }, inView: false, }, { when: 'view zoom is less than minZoom (with maxZoom)', minZoom: 2, maxZoom: 4, view: { resolution: 1, zoom: 1, }, inView: false, }, { when: 'view zoom is equal to minZoom', minZoom: 2, view: { resolution: 2, zoom: 2, }, inView: false, }, { when: 'view zoom is equal to minZoom (with maxZoom)', minZoom: 2, maxZoom: 4, view: { resolution: 2, zoom: 2, }, inView: false, }, { when: 'view zoom is greater than minZoom', minZoom: 2, view: { resolution: 3, zoom: 3, }, inView: true, }, { when: 'view zoom is greater than minZoom (with maxZoom)', minZoom: 2, maxZoom: 4, view: { resolution: 3, zoom: 3, }, inView: true, }, { when: 'view zoom is equal to maxZoom', maxZoom: 4, view: { resolution: 4, zoom: 4, }, inView: true, }, { when: 'view zoom is equal to maxZoom (with minZoom)', minZoom: 2, maxZoom: 4, view: { resolution: 4, zoom: 4, }, inView: true, }, { when: 'view zoom is greater than maxZoom', maxZoom: 4, view: { resolution: 5, zoom: 5, }, inView: false, }, { when: 'view zoom is greater than maxZoom (with minZoom)', minZoom: 2, maxZoom: 4, view: { resolution: 5, zoom: 5, }, inView: false, }, { when: 'view resolution is less than minResolution', minResolution: 2, view: { resolution: 1, zoom: 1, }, inView: false, }, { when: 'view resolution is less than minResolution (with maxResolution)', minResolution: 2, maxResolution: 4, view: { resolution: 1, zoom: 1, }, inView: false, }, { when: 'view resolution is equal to minResolution', minResolution: 2, view: { resolution: 2, zoom: 2, }, inView: true, }, { when: 'view resolution is equal to minResolution (with maxResolution)', minResolution: 2, maxResolution: 4, view: { resolution: 2, zoom: 2, }, inView: true, }, { when: 'view resolution is greater than minResolution', minResolution: 2, view: { resolution: 3, zoom: 3, }, inView: true, }, { when: 'view resolution is greater than minResolution (with maxResolution)', minResolution: 2, maxResolution: 4, view: { resolution: 3, zoom: 3, }, inView: true, }, { when: 'view resolution is equal to maxResolution', maxResolution: 4, view: { resolution: 4, zoom: 4, }, inView: false, }, { when: 'view resolution is equal to maxResolution (with minResolution)', minResolution: 2, maxResolution: 4, view: { resolution: 4, zoom: 4, }, inView: false, }, { when: 'view resolution is greater than maxResolution', maxResolution: 4, view: { resolution: 5, zoom: 5, }, inView: false, }, { when: 'view resolution is greater than maxResolution (with minResolution)', minResolution: 2, maxResolution: 4, view: { resolution: 5, zoom: 5, }, inView: false, }, ]; cases.forEach(function (c, i) { it('returns ' + c.inView + ' when ' + c.when, function () { if ('visible' in c) { layer.setVisible(c.visible); } if ('minZoom' in c) { layer.setMinZoom(c.minZoom); } if ('maxZoom' in c) { layer.setMaxZoom(c.maxZoom); } if ('minResolution' in c) { layer.setMinResolution(c.minResolution); } if ('maxResolution' in c) { layer.setMaxResolution(c.maxResolution); } const layerState = layer.getLayerState(); expect(inView(layerState, c.view)).to.be(c.inView); }); }); }); describe('#getLayerState', function () { let layer; beforeEach(function () { layer = new Layer({ source: new Source({ projection: getProjection('EPSG:4326'), }), }); }); afterEach(function () { layer.dispose(); }); it('returns a layerState from the properties values', function () { layer.setOpacity(1 / 3); layer.setVisible(false); layer.setMaxResolution(500); layer.setMinResolution(0.25); layer.setZIndex(10); expect(layer.getLayerState()).to.eql({ layer: layer, opacity: 0.33, visible: false, managed: true, sourceState: 'ready', extent: undefined, zIndex: 10, maxResolution: 500, minResolution: 0.25, minZoom: -Infinity, maxZoom: Infinity, }); }); it('returns a layerState with clamped values', function () { layer.setOpacity(-1.5); layer.setVisible(false); let state = layer.getLayerState(); expect(state.opacity).to.be(0); expect(state.visible).to.be(false); layer.setOpacity(3); layer.setVisible(true); state = layer.getLayerState(); expect(state.opacity).to.be(1); expect(state.visible).to.be(true); }); }); describe('#getSource', function () { it('gets the layer source', function () { const source = new Source({projection: getProjection('EPSG:4326')}); const layer = new Layer({source: source}); expect(layer.getSource()).to.be(source); }); }); describe('#set("source", source)', function () { const projection = getProjection('EPSG:4326'); it('sets the layer source', function () { const layer = new Layer({ source: new Source({projection: projection}), }); const source = new Source({projection: projection}); layer.set('source', source); expect(layer.getSource()).to.be(source); }); it('calls changed', function () { const layer = new Layer({ source: new Source({projection: projection}), }); sinon.spy(layer, 'changed'); const source = new Source({projection: projection}); layer.set('source', source); expect(layer.changed.calledOnce).to.be(true); }); it('sets up event listeners', function () { sinon.spy(Layer.prototype, 'handleSourceChange_'); const first = new Source({projection: projection}); const layer = new Layer({source: first}); first.setState('ready'); expect(layer.handleSourceChange_.calledOnce).to.be(true); const second = new Source({projection: projection}); layer.set('source', second); expect(layer.handleSourceChange_.calledOnce).to.be(true); second.setState('ready'); expect(layer.handleSourceChange_.callCount).to.be(2); // remove spy Layer.prototype.handleSourceChange_.restore(); }); }); describe('#setSource()', function () { const projection = getProjection('EPSG:4326'); it('sets the layer source', function () { const layer = new Layer({ source: new Source({projection: projection}), }); const source = new Source({projection: projection}); layer.setSource(source); expect(layer.getSource()).to.be(source); }); it('calls changed', function () { const layer = new Layer({ source: new Source({projection: projection}), }); sinon.spy(layer, 'changed'); const source = new Source({projection: projection}); layer.setSource(source); expect(layer.changed.calledOnce).to.be(true); }); it('sets up event listeners', function () { sinon.spy(Layer.prototype, 'handleSourceChange_'); const first = new Source({projection: projection}); const layer = new Layer({source: first}); first.setState('ready'); expect(layer.handleSourceChange_.calledOnce).to.be(true); const second = new Source({projection: projection}); layer.setSource(second); expect(layer.handleSourceChange_.calledOnce).to.be(true); second.setState('ready'); expect(layer.handleSourceChange_.callCount).to.be(2); // remove spy Layer.prototype.handleSourceChange_.restore(); }); }); describe('#setOpacity', function () { let layer; beforeEach(function () { layer = new Layer({ source: new Source({ projection: getProjection('EPSG:4326'), }), }); }); afterEach(function () { layer.dispose(); }); it('accepts a positive number', function () { layer.setOpacity(0.3); expect(layer.getOpacity()).to.be(0.3); }); it('throws on types other than number', function () { function set() { layer.setOpacity('foo'); } expect(set).to.throwException(); }); it('triggers a change event', function () { const listener = sinon.spy(); layer.on('propertychange', listener); layer.setOpacity(0.4); expect(listener.calledOnce).to.be(true); }); }); describe('#setVisible', function () { let layer; beforeEach(function () { layer = new Layer({ source: new Source({ projection: getProjection('EPSG:4326'), }), }); }); afterEach(function () { layer.dispose(); }); it('sets visible property', function () { layer.setVisible(false); expect(layer.getVisible()).to.be(false); layer.setVisible(true); expect(layer.getVisible()).to.be(true); }); it('fires a change event', function () { const listener = sinon.spy(); layer.on('propertychange', listener); layer.setVisible(false); expect(listener.callCount).to.be(1); layer.setVisible(true); expect(listener.callCount).to.be(2); }); }); describe('map property', () => { it('is set when a layer is added to a map', () => { const map = new Map({}); const layer = new Layer({}); map.addLayer(layer); expect(layer.get(Property.MAP)).to.be(map); }); it('is set when a layer is added to a map in the constructor', () => { const layer = new Layer({}); const map = new Map({layers: [layer]}); expect(layer.get(Property.MAP)).to.be(map); }); it('is set when a layer is added to a group', () => { const layer = new Layer({}); const group = new Group(); const map = new Map({}); map.addLayer(group); group.getLayers().push(layer); expect(layer.get(Property.MAP)).to.be(map); }); it('is set when a layer is added to a group set in the constructor', () => { const layer = new Layer({}); const group = new Group(); const map = new Map({layers: [group]}); group.getLayers().push(layer); expect(layer.get(Property.MAP)).to.be(map); }); it('is set when a layer already added to a group set in the constructor', () => { const layer = new Layer({}); const group = new Group({layers: [layer]}); const map = new Map({layers: [group]}); expect(layer.get(Property.MAP)).to.be(map); }); it('is removed when a layer is removed from the map', () => { const map = new Map({}); const layer = new Layer({}); map.addLayer(layer); expect(layer.get(Property.MAP)).to.be(map); map.removeLayer(layer); expect(layer.get(Property.MAP)).to.be(null); }); it('is removed when a layer added in the constructor is removed from the map', () => { const layer = new Layer({}); const map = new Map({layers: [layer]}); expect(layer.get(Property.MAP)).to.be(map); map.removeLayer(layer); expect(layer.get(Property.MAP)).to.be(null); }); it('is removed when a layer is removed from a group', () => { const layer = new Layer({}); const group = new Group({layers: [layer]}); const map = new Map({layers: [group]}); expect(layer.get(Property.MAP)).to.be(map); group.getLayers().remove(layer); expect(layer.get(Property.MAP)).to.be(null); }); }); describe('#setMap (unmanaged layer)', function () { let map; beforeEach(function () { map = new Map({}); }); describe('with map in constructor options', function () { it('renders the layer', function () { const layer = new Layer({ map: map, }); const frameState = { layerStatesArray: [], }; map.dispatchEvent( new RenderEvent('precompose', null, frameState, null) ); expect(frameState.layerStatesArray.length).to.be(1); const layerState = frameState.layerStatesArray[0]; expect(layerState.layer).to.equal(layer); }); }); describe('setMap sequences', function () { let mapRenderSpy; beforeEach(function () { mapRenderSpy = sinon.spy(map, 'render'); }); afterEach(function () { mapRenderSpy.restore(); }); it('requests a render frame', function () { const layer = new Layer({}); layer.setMap(map); expect(mapRenderSpy.callCount).to.be(1); layer.setMap(null); expect(mapRenderSpy.callCount).to.be(2); layer.setMap(map); expect(mapRenderSpy.callCount).to.be(3); }); }); describe('zIndex for unmanaged layers', function () { let frameState, layer; beforeEach(function () { layer = new Layer({ map: map, }); frameState = { layerStatesArray: [], }; }); afterEach(function () { layer.setMap(null); }); it('has Infinity as zIndex when not configured otherwise', function () { map.dispatchEvent( new RenderEvent('precompose', null, frameState, null) ); const layerState = frameState.layerStatesArray[0]; expect(layerState.zIndex).to.be(Infinity); }); it('respects the configured zIndex', function () { [-5, 0, 42].forEach((index) => { layer.setZIndex(index); map.dispatchEvent( new RenderEvent('precompose', null, frameState, null) ); const layerState = frameState.layerStatesArray[0]; frameState.layerStatesArray.length = 0; expect(layerState.zIndex).to.be(index); }); }); }); }); });