text: import Feature from '../../../src/ol/Feature.js';
  import ImageState from '../../../src/ol/ImageState.js';
  import Map from '../../../src/ol/Map.js';
  import MapEvent from '../../../src/ol/MapEvent.js';
  import Overlay from '../../../src/ol/Overlay.js';
  import View from '../../../src/ol/View.js';
  import LineString from '../../../src/ol/geom/LineString.js';
  import {TOUCH} from '../../../src/ol/has.js';
  import {focus} from '../../../src/ol/events/condition.js';
  import {defaults as defaultInteractions} from '../../../src/ol/interaction.js';
  import {get as getProjection} from '../../../src/ol/proj.js';
  import GeoJSON from '../../../src/ol/format/GeoJSON.js';
  import DragPan from '../../../src/ol/interaction/DragPan.js';
  import DoubleClickZoom from '../../../src/ol/interaction/DoubleClickZoom.js';
  import Interaction from '../../../src/ol/interaction/Interaction.js';
  import MouseWheelZoom from '../../../src/ol/interaction/MouseWheelZoom.js';
  import PinchZoom from '../../../src/ol/interaction/PinchZoom.js';
  import ImageLayer from '../../../src/ol/layer/Image.js';
  import TileLayer from '../../../src/ol/layer/Tile.js';
  import VectorLayer from '../../../src/ol/layer/Vector.js';
  import IntermediateCanvasRenderer from '../../../src/ol/renderer/canvas/IntermediateCanvas.js';
  import ImageStatic from '../../../src/ol/source/ImageStatic.js';
  import VectorSource from '../../../src/ol/source/Vector.js';
  import XYZ from '../../../src/ol/source/XYZ.js';
  
  describe('ol.Map', function() {
  
    describe('constructor', function() {
      it('creates a new map', function() {
        const map = new Map({});
        expect(map).to.be.a(Map);
      });
  
      it('creates a set of default interactions', function() {
        const map = new Map({});
        const interactions = map.getInteractions();
        const length = interactions.getLength();
        expect(length).to.be.greaterThan(0);
  
        for (let i = 0; i < length; ++i) {
          expect(interactions.item(i).getMap()).to.be(map);
        }
      });
  
      it('creates the viewport', function() {
        const map = new Map({});
        const viewport = map.getViewport();
        const className = 'ol-viewport' + (TOUCH ? ' ol-touch' : '');
        expect(viewport.className).to.be(className);
      });
  
      it('creates the overlay containers', function() {
        const map = new Map({});
        const container = map.getOverlayContainer();
        expect(container.className).to.be('ol-overlaycontainer');
  
        const containerStop = map.getOverlayContainerStopEvent();
        expect(containerStop.className).to.be('ol-overlaycontainer-stopevent');
      });
  
    });
  
    describe('#addLayer()', function() {
      it('adds a layer to the map', function() {
        const map = new Map({});
        const layer = new TileLayer();
        map.addLayer(layer);
  
        expect(map.getLayers().item(0)).to.be(layer);
      });
  
      it('throws if a layer is added twice', function() {
        const map = new Map({});
        const layer = new TileLayer();
        map.addLayer(layer);
  
        const call = function() {
          map.addLayer(layer);
        };
        expect(call).to.throwException();
      });
    });
  
    describe('#addInteraction()', function() {
      it('adds an interaction to the map', function() {
        const map = new Map({});
        const interaction = new Interaction({});
  
        const before = map.getInteractions().getLength();
        map.addInteraction(interaction);
        const after = map.getInteractions().getLength();
        expect(after).to.be(before + 1);
        expect(interaction.getMap()).to.be(map);
      });
    });
  
    describe('#removeInteraction()', function() {
      it('removes an interaction from the map', function() {
        const map = new Map({});
        const interaction = new Interaction({});
  
        const before = map.getInteractions().getLength();
        map.addInteraction(interaction);
  
        map.removeInteraction(interaction);
        expect(map.getInteractions().getLength()).to.be(before);
  
        expect(interaction.getMap()).to.be(null);
      });
    });
  
    describe('movestart/moveend event', function() {
  
      let target, view, map;
  
      beforeEach(function() {
        target = document.createElement('div');
  
        const style = target.style;
        style.position = 'absolute';
        style.left = '-1000px';
        style.top = '-1000px';
        style.width = '360px';
        style.height = '180px';
        document.body.appendChild(target);
  
        view = new View({
          projection: 'EPSG:4326'
        });
        map = new Map({
          target: target,
          view: view,
          layers: [
            new TileLayer({
              source: new XYZ({
                url: '#{x}/{y}/{z}'
              })
            })
          ]
        });
      });
  
      afterEach(function() {
        map.dispose();
        document.body.removeChild(target);
      });
  
      it('are fired only once after view changes', function(done) {
        const center = [10, 20];
        const zoom = 3;
        let startCalls = 0;
        let endCalls = 0;
        map.on('movestart', function() {
          ++startCalls;
          expect(startCalls).to.be(1);
        });
        map.on('moveend', function() {
          ++endCalls;
          expect(endCalls).to.be(1);
          expect(view.getCenter()).to.eql(center);
          expect(view.getZoom()).to.be(zoom);
          window.setTimeout(done, 1000);
        });
  
        view.setCenter(center);
        view.setZoom(zoom);
      });
  
      it('are fired in sequence', function(done) {
        view.setCenter([0, 0]);
        view.setResolution(0.703125);
        map.renderSync();
        const center = [10, 20];
        const zoom = 3;
        const calls = [];
        map.on('movestart', function(e) {
          calls.push('start');
          expect(calls).to.eql(['start']);
          expect(e.frameState.viewState.center).to.eql([0, 0]);
          expect(e.frameState.viewState.resolution).to.be(0.703125);
        });
        map.on('moveend', function() {
          calls.push('end');
          expect(calls).to.eql(['start', 'end']);
          expect(view.getCenter()).to.eql(center);
          expect(view.getZoom()).to.be(zoom);
          done();
        });
  
        view.setCenter(center);
        view.setZoom(zoom);
      });
  
    });
  
    describe('rendercomplete event', function() {
  
      let map;
      beforeEach(function() {
        const target = document.createElement('div');
        target.style.width = target.style.height = '100px';
        document.body.appendChild(target);
        map = new Map({
          target: target,
          layers: [
            new TileLayer({
              source: new XYZ({
                url: 'spec/ol/data/osm-{z}-{x}-{y}.png'
              })
            }),
            new ImageLayer({
              source: new ImageStatic({
                url: 'spec/ol/data/osm-0-0-0.png',
                imageExtent: getProjection('EPSG:3857').getExtent(),
                projection: 'EPSG:3857'
              })
            }),
            new VectorLayer({
              source: new VectorSource({
                url: 'spec/ol/data/point.json',
                format: new GeoJSON()
              })
            })
          ]
        });
      });
  
      afterEach(function() {
        document.body.removeChild(map.getTargetElement());
        map.setTarget(null);
        map.dispose();
      });
  
      it('triggers when all tiles and sources are loaded and faded in', function(done) {
        map.once('rendercomplete', function() {
          const layers = map.getLayers().getArray();
          expect(map.tileQueue_.getTilesLoading()).to.be(0);
          expect(layers[1].getSource().image_.getState()).to.be(ImageState.LOADED);
          expect(layers[2].getSource().getFeatures().length).to.be(1);
          done();
        });
        map.setView(new View({
          center: [0, 0],
          zoom: 0
        }));
      });
  
    });
  
    describe('#getFeaturesAtPixel', function() {
  
      let target, map;
      beforeEach(function() {
        target = document.createElement('div');
        target.style.width = target.style.height = '100px';
        document.body.appendChild(target);
        map = new Map({
          target: target,
          layers: [new VectorLayer({
            source: new VectorSource({
              features: [new Feature(new LineString([[-50, 0], [50, 0]]))]
            })
          })],
          view: new View({
            center: [0, 0],
            zoom: 17
          })
        });
        map.renderSync();
      });
      afterEach(function() {
        document.body.removeChild(target);
      });
  
      it('returns null if no feature was found', function() {
        const features = map.getFeaturesAtPixel([0, 0]);
        expect(features).to.be(null);
      });
  
      it('returns an array of found features', function() {
        const features = map.getFeaturesAtPixel([50, 50]);
        expect(features).to.be.an(Array);
        expect(features[0]).to.be.an(Feature);
      });
  
      it('returns an array of found features with declutter: true', function() {
        const layer = map.getLayers().item(0);
        map.removeLayer(layer);
        const otherLayer = new VectorLayer({
          declutter: true,
          source: layer.getSource()
        });
        map.addLayer(otherLayer);
        map.renderSync();
        const features = map.getFeaturesAtPixel([50, 50]);
        expect(features).to.be.an(Array);
        expect(features[0]).to.be.a(Feature);
      });
  
      it('respects options', function() {
        const otherLayer = new VectorLayer({
          source: new VectorSource
        });
        map.addLayer(otherLayer);
        const features = map.getFeaturesAtPixel([50, 50], {
          layerFilter: function(layer) {
            return layer == otherLayer;
          }
        });
        expect(features).to.be(null);
      });
  
    });
  
    describe('#forEachLayerAtPixel()', function() {
  
      let target, map, original, log;
  
      beforeEach(function(done) {
        log = [];
        original = IntermediateCanvasRenderer.prototype.forEachLayerAtCoordinate;
        IntermediateCanvasRenderer.prototype.forEachLayerAtCoordinate = function(coordinate) {
          log.push(coordinate.slice());
        };
  
        target = document.createElement('div');
        const style = target.style;
        style.position = 'absolute';
        style.left = '-1000px';
        style.top = '-1000px';
        style.width = '360px';
        style.height = '180px';
        document.body.appendChild(target);
  
        map = new Map({
          target: target,
          view: new View({
            center: [0, 0],
            zoom: 1
          }),
          layers: [
            new TileLayer({
              source: new XYZ()
            }),
            new TileLayer({
              source: new XYZ()
            }),
            new TileLayer({
              source: new XYZ()
            })
          ]
        });
  
        map.once('postrender', function() {
          done();
        });
      });
  
      afterEach(function() {
        IntermediateCanvasRenderer.prototype.forEachLayerAtCoordinate = original;
        map.dispose();
        document.body.removeChild(target);
        log = null;
      });
  
      it('calls each layer renderer with the same coordinate', function() {
        const pixel = [10, 20];
        map.forEachLayerAtPixel(pixel, function() {});
        expect(log.length).to.equal(3);
        expect(log[0].length).to.equal(2);
        expect(log[0]).to.eql(log[1]);
        expect(log[1]).to.eql(log[2]);
      });
  
    });
  
    describe('#render()', function() {
  
      let target, map;
  
      beforeEach(function() {
        target = document.createElement('div');
        const style = target.style;
        style.position = 'absolute';
        style.left = '-1000px';
        style.top = '-1000px';
        style.width = '360px';
        style.height = '180px';
        document.body.appendChild(target);
        map = new Map({
          target: target,
          view: new View({
            projection: 'EPSG:4326',
            center: [0, 0],
            resolution: 1
          })
        });
      });
  
      afterEach(function() {
        map.dispose();
        document.body.removeChild(target);
      });
  
      it('is called when the view.changed() is called', function() {
        const view = map.getView();
  
        const spy = sinon.spy(map, 'render');
        view.changed();
        expect(spy.callCount).to.be(1);
      });
  
      it('is not called on view changes after the view has been removed', function() {
        const view = map.getView();
        map.setView(null);
  
        const spy = sinon.spy(map, 'render');
        view.changed();
        expect(spy.callCount).to.be(0);
      });
  
      it('calls renderFrame_ and results in an postrender event', function(done) {
  
        const spy = sinon.spy(map, 'renderFrame_');
        map.render();
        map.once('postrender', function(event) {
          expect(event).to.be.a(MapEvent);
          expect(typeof spy.firstCall.args[0]).to.be('number');
          spy.restore();
          const frameState = event.frameState;
          expect(frameState).not.to.be(null);
          done();
        });
  
      });
  
      it('uses the same render frame for subsequent calls', function(done) {
        map.render();
        const id1 = map.animationDelayKey_;
        let id2 = null;
        map.once('postrender', function() {
          expect(id2).to.be(id1);
          done();
        });
        map.render();
        id2 = map.animationDelayKey_;
      });
  
      it('creates a new render frame after renderSync()', function(done) {
        let id2 = null;
        map.render();
        const id1 = map.animationDelayKey_;
        map.once('postrender', function() {
          expect(id2).to.not.be(id1);
          done();
        });
        map.renderSync();
        id2 = map.animationDelayKey_;
      });
  
      it('results in an postrender event (for zero height map)', function(done) {
        target.style.height = '0px';
        map.updateSize();
  
        map.render();
        map.once('postrender', function(event) {
          expect(event).to.be.a(MapEvent);
          const frameState = event.frameState;
          expect(frameState).to.be(null);
          done();
        });
  
      });
  
      it('results in an postrender event (for zero width map)', function(done) {
        target.style.width = '0px';
        map.updateSize();
  
        map.render();
        map.once('postrender', function(event) {
          expect(event).to.be.a(MapEvent);
          const frameState = event.frameState;
          expect(frameState).to.be(null);
          done();
        });
  
      });
  
    });
  
    describe('dispose', function() {
      let map;
  
      beforeEach(function() {
        map = new Map({
          target: document.createElement('div')
        });
      });
  
      it('removes the viewport from its parent', function() {
        map.dispose();
        expect(map.getViewport().parentNode).to.be(null);
      });
  
      it('removes window listeners', function() {
        map.dispose();
        expect(map.handleResize_).to.be(undefined);
      });
    });
  
    describe('#setTarget', function() {
      let map;
  
      beforeEach(function() {
        map = new Map({
          target: document.createElement('div')
        });
        expect(map.handleResize_).to.be.ok();
      });
  
      describe('call setTarget with null', function() {
        it('unregisters the viewport resize listener', function() {
          map.setTarget(null);
          expect(map.handleResize_).to.be(undefined);
        });
      });
  
      describe('call setTarget with an element', function() {
        it('registers a viewport resize listener', function() {
          map.setTarget(null);
          map.setTarget(document.createElement('div'));
          expect(map.handleResize_).to.be.ok();
        });
      });
  
    });
  
    describe('create interactions', function() {
  
      let options;
  
      beforeEach(function() {
        options = {
          altShiftDragRotate: false,
          doubleClickZoom: false,
          keyboard: false,
          mouseWheelZoom: false,
          shiftDragZoom: false,
          dragPan: false,
          pinchRotate: false,
          pinchZoom: false
        };
      });
  
      describe('create mousewheel interaction', function() {
        it('creates mousewheel interaction', function() {
          options.mouseWheelZoom = true;
          const interactions = defaultInteractions(options);
          expect(interactions.getLength()).to.eql(1);
          expect(interactions.item(0)).to.be.a(MouseWheelZoom);
          expect(interactions.item(0).constrainResolution_).to.eql(false);
          expect(interactions.item(0).useAnchor_).to.eql(true);
          interactions.item(0).setMouseAnchor(false);
          expect(interactions.item(0).useAnchor_).to.eql(false);
          expect(interactions.item(0).condition_).to.not.be(focus);
        });
        it('uses the focus condition when onFocusOnly option is set', function() {
          options.onFocusOnly = true;
          options.mouseWheelZoom = true;
          const interactions = defaultInteractions(options);
          expect(interactions.item(0).condition_).to.be(focus);
        });
      });
  
      describe('create dragpan interaction', function() {
        it('creates dragpan interaction', function() {
          options.dragPan = true;
          const interactions = defaultInteractions(options);
          expect(interactions.getLength()).to.eql(1);
          expect(interactions.item(0)).to.be.a(DragPan);
          expect(interactions.item(0).condition_).to.not.be(focus);
        });
        it('uses the focus condition when onFocusOnly option is set', function() {
          options.onFocusOnly = true;
          options.dragPan = true;
          const interactions = defaultInteractions(options);
          expect(interactions.item(0).condition_).to.be(focus);
        });
      });
  
      describe('create pinchZoom interaction', function() {
        it('creates pinchZoom interaction', function() {
          options.pinchZoom = true;
          const interactions = defaultInteractions(options);
          expect(interactions.getLength()).to.eql(1);
          expect(interactions.item(0)).to.be.a(PinchZoom);
          expect(interactions.item(0).constrainResolution_).to.eql(false);
        });
      });
  
      describe('set constrainResolution option', function() {
        it('set constrainResolution option', function() {
          options.pinchZoom = true;
          options.mouseWheelZoom = true;
          options.constrainResolution = true;
          const interactions = defaultInteractions(options);
          expect(interactions.getLength()).to.eql(2);
          expect(interactions.item(0)).to.be.a(PinchZoom);
          expect(interactions.item(0).constrainResolution_).to.eql(true);
          expect(interactions.item(1)).to.be.a(MouseWheelZoom);
          expect(interactions.item(1).constrainResolution_).to.eql(true);
        });
      });
  
      describe('create double click interaction', function() {
  
        beforeEach(function() {
          options.doubleClickZoom = true;
        });
  
        describe('default zoomDelta', function() {
          it('create double click interaction with default delta', function() {
            const interactions = defaultInteractions(options);
            expect(interactions.getLength()).to.eql(1);
            expect(interactions.item(0)).to.be.a(DoubleClickZoom);
            expect(interactions.item(0).delta_).to.eql(1);
          });
        });
  
        describe('set zoomDelta', function() {
          it('create double click interaction with set delta', function() {
            options.zoomDelta = 7;
            const interactions = defaultInteractions(options);
            expect(interactions.getLength()).to.eql(1);
            expect(interactions.item(0)).to.be.a(DoubleClickZoom);
            expect(interactions.item(0).delta_).to.eql(7);
          });
        });
      });
  
      describe('#getEventPixel', function() {
  
        let target;
  
        beforeEach(function() {
          target = document.createElement('div');
          target.style.position = 'absolute';
          target.style.top = '10px';
          target.style.left = '20px';
          target.style.width = '800px';
          target.style.height = '400px';
  
          document.body.appendChild(target);
        });
        afterEach(function() {
          document.body.removeChild(target);
        });
  
        it('works with touchend events', function() {
  
          const map = new Map({
            target: target
          });
  
          const browserEvent = {
            type: 'touchend',
            target: target,
            changedTouches: [{
              clientX: 100,
              clientY: 200
            }]
          };
          const position = map.getEventPixel(browserEvent);
          // 80 = clientX - target.style.left
          expect(position[0]).to.eql(80);
          // 190 = clientY - target.style.top
          expect(position[1]).to.eql(190);
        });
      });
  
      describe('#getOverlayById()', function() {
        let target, map, overlay, overlay_target;
  
        beforeEach(function() {
          target = document.createElement('div');
          const style = target.style;
          style.position = 'absolute';
          style.left = '-1000px';
          style.top = '-1000px';
          style.width = '360px';
          style.height = '180px';
          document.body.appendChild(target);
          map = new Map({
            target: target,
            view: new View({
              projection: 'EPSG:4326',
              center: [0, 0],
              resolution: 1
            })
          });
          overlay_target = document.createElement('div');
        });
  
        afterEach(function() {
          map.removeOverlay(overlay);
          map.dispose();
          document.body.removeChild(target);
        });
  
        it('returns an overlay by id', function() {
          overlay = new Overlay({
            id: 'foo',
            element: overlay_target,
            position: [0, 0]
          });
          map.addOverlay(overlay);
          expect(map.getOverlayById('foo')).to.be(overlay);
        });
  
        it('returns null when no overlay is found', function() {
          overlay = new Overlay({
            id: 'foo',
            element: overlay_target,
            position: [0, 0]
          });
          map.addOverlay(overlay);
          expect(map.getOverlayById('bar')).to.be(null);
        });
  
        it('returns null after removing overlay', function() {
          overlay = new Overlay({
            id: 'foo',
            element: overlay_target,
            position: [0, 0]
          });
          map.addOverlay(overlay);
          expect(map.getOverlayById('foo')).to.be(overlay);
          map.removeOverlay(overlay);
          expect(map.getOverlayById('foo')).to.be(null);
        });
  
      });
  
    });
  
  });
  