Angular2 + Openlayers3: Test fails when map render

2019-09-15 18:36发布

问题:

I have create an Angular2 app (+Webpack). I added a component which shows an Openlayers3 map.It also adds a few markers and a polygon. I am reasonable happy with the functionality, but writing a test for it nearly proves impossible.

This is the test source code: ( I have not added any expect statements yet. The test seems to break when Openlayers tries to render the map.

/* tslint:disable:no-unused-variable */
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {DebugElement, NO_ERRORS_SCHEMA} from '@angular/core';
import * as ol from 'openlayers';


import {OlComponent} from './ol.component';

let comp: OlComponent;

describe('OlComponent', () => {

    let fixture: ComponentFixture<OlComponent>;
    let component: OlComponent;


    let element: any;

    beforeEach(async(() => {
        TestBed.configureTestingModule({
            declarations: [
                OlComponent
            ],
            schemas: [NO_ERRORS_SCHEMA],

        })

    }))


    it('should display the map', () => {
        TestBed.overrideComponent(OlComponent, {
            set: {
                template: '<div id="map" style="width:10px;height:10px"></div>'
            }
        });
        fixture = TestBed.createComponent(OlComponent);
        console.log(fixture);
        component = fixture.componentInstance;


        element = fixture.nativeElement;
        console.log(element);
        let map = element.querySelector('#map');
        map.style.width = "100px";
        map.style.height = "100px";

        component.ngOnInit();
        fixture.detectChanges

    });
});

All my efforts so far have only produced this error message when running the test (see source code(s) below.

OlComponent
✖ should display the map
  Chrome 53.0.2785 (Linux 0.0.0)
TypeError: Cannot read property 'length' of undefined
    at Kc (webpack:///~/openlayers/dist/ol.js:43:372 <- karma-shim.js:70246:398)
    at Object.fromLonLat (webpack:///~/openlayers/dist/ol.js:768:188 <- karma-shim.js:70971:196)
    at OlComponent.createMap (webpack:///src/app/ol-maps/ol.component.ts:9:1914 <- karma-shim.js:70159:1929)
    at OlComponent.ngOnInit (webpack:///src/app/ol-maps/ol.component.ts:9:6364 <- karma-shim.js:70159:6369)
    at Object.<anonymous> (webpack:///src/app/ol-maps/ol.component.spec.ts:33:0 <- karma-shim.js:53653:19)
    at ZoneDelegate.invoke (webpack:///~/zone.js/dist/zone.js:232:0 <- karma-shim.js:40990:26)
    at ProxyZoneSpec.onInvoke (webpack:///~/zone.js/dist/proxy.js:79:0 <- karma-shim.js:40654:39)
    at ZoneDelegate.invoke (webpack:///~/zone.js/dist/zone.js:231:0 <- karma-shim.js:40989:32)
    at Zone.run (webpack:///~/zone.js/dist/zone.js:114:0 <- karma-shim.js:40872:43)
    at Object.<anonymous> (webpack:///~/zone.js/dist/jasmine-patch.js:102:0 <- karma-shim.js:40369:34)
    at webpack:///~/@angular/core/bundles/core-testing.umd.js:91:0 <- karma-shim.js:3519:21
    at ZoneDelegate.invoke (webpack:///~/zone.js/dist/zone.js:232:0 <- karma-shim.js:40990:26)
    at AsyncTestZoneSpec.onInvoke (webpack:///~/zone.js/dist/async-test.js:49:0 <- karma-shim.js:39959:39)
    at ProxyZoneSpec.onInvoke (webpack:///~/zone.js/dist/proxy.js:76:0 <- karma-shim.js:40651:39)
    at ZoneDelegate.invoke (webpack:///~/zone.js/dist/zone.js:231:0 <- karma-shim.js:40989:32)
    at Zone.run (webpack:///~/zone.js/dist/zone.js:114:0 <- karma-shim.js:40872:43)
    at AsyncTestZoneSpec._finishCallback (webpack:///~/@angular/core/bundles/core-testing.umd.js:86:0 <- karma-shim.js:3514:29)
    at webpack:///~/zone.js/dist/async-test.js:38:0 <- karma-shim.js:39948:31
    at ZoneDelegate.invokeTask (webpack:///~/zone.js/dist/zone.js:265:0 <- karma-shim.js:41023:35)
    at Zone.runTask (webpack:///~/zone.js/dist/zone.js:154:0 <- karma-shim.js:40912:47)
    at ZoneTask.invoke (webpack:///~/zone.js/dist/zone.js:335:0 <- karma-shim.js:41093:33)
    at data.args.(anonymous function) (webpack:///~/zone.js/dist/zone.js:970:0 <- karma-shim.js:41728:25)

OK, here is the source code of the files involved: I am starting of with a service providing Openlayers3 to the component

import {Injectable} from '@angular/core';
import * as ol from 'openlayers';


@Injectable()
export class OlService {

    get(): any {
        return ol;
    }
}

Moving on to the component. (I removed the layers URLs)

import {Component, OnInit, Input} from '@angular/core';
import {OlService} from './ol.service';

@Component({
    selector: 'my-map',
    templateUrl: './ol.component.html',
    styleUrls: ['./ol.component.scss'],
    providers: [OlService]
})
export class OlComponent implements OnInit {

    @Input() lnglat: [number, number];
    @Input() zoom: number;

    private map;
    public layers = [];
    private vectorSource;

    constructor(private olService: OlService) {

    }

    createMap = () => {
        let ol = this.olService.get();
        this.vectorSource = new ol.source.Vector({});
        // define layers

        let OSM = new ol.layer.Tile({
            source: new ol.source.OSM()
        });
        OSM.set('name', 'Openstreetmap');


        let geography = new ol.layer.Tile({
            source: new ol.source.TileJSON({
                url: '',
                crossOrigin: '',
            }),
            visible: false
        });
        geography.set('name', 'Geography');

        let boundaries = new ol.layer.Tile({
            opacity: 0.5,

            source: new ol.source.TileWMS({
                url: '',
                params: {
                    'LAYERS': 'fwsys:fwsys_region',
                    'TILED': true,
                    'transparent': 'true',
                    'format': 'image/png'
                },
                serverType: 'geoserver',
                projection: ol.proj.get('EPSG:3857')
            })
        });
        boundaries.set('name', 'Boundaries');
        let vector = new ol.layer.Vector({
            source: this.vectorSource
        });

        this.map = new ol.Map({
            target: 'map',
            layers: [OSM, geography, vector, boundaries],
            view: new ol.View({
                center: ol.proj.fromLonLat(this.lnglat),
                zoom: this.zoom,
                projection: ol.proj.get('EPSG:3857')
            })
        });

        let select_interaction = new ol.interaction.Select();

        this.map.addInteraction(select_interaction);

        // add popup for all features
        let container = document.getElementById('popup');
        let content = document.getElementById('popup-content');
        let closer = document.getElementById('popup-closer');


        let popup = new ol.Overlay({
            element: container,
            autoPan: true,
            positioning: 'bottom-center',
            stopEvent: false,
            offset: [0, -5]
        });

        closer.onclick = function () {
            popup.setPosition(undefined);
            closer.blur();
            return false;
        };
        this.map.addOverlay(popup);

        this.map.on('click', (evt) => {
            let feature = this.map.forEachFeatureAtPixel(evt.pixel, (feat) => {
                return feat;
            });
            if (feature) {
                let coordinate = evt.coordinate;
                content.innerHTML = feature.get('name');
                popup.setPosition(coordinate);
            }


        });
        this.addLayerSwitcher([OSM, geography, boundaries]);
        this.addMarker([174.76, -37.10], 'Close to Auckland', 'akl1');
        this.addMarker([173.76, -37.10], 'Out in in waters', 'pacific1');

        this.addPolygon([[174.76, -37.18], [176.76, -37.18], [176.76, -38.18], [174.76, -38.18]], 'Hamilton', 'id_hamilton');
    };

    addPolygon = (polygon: [[number, number]], name: string, id: string) => {
        let ol = this.olService.get();
        let projectedPolygon = [];

        for (let poly of polygon) {
            projectedPolygon.push(ol.proj.transform(poly, 'EPSG:4326', 'EPSG:3857'));
        }

        let p = new ol.geom.Polygon([projectedPolygon]);

        let featurething = new ol.Feature({
            name: name,
            id: id,
            geometry: p
        });

        this.vectorSource.addFeature(featurething);

    };

    addMarker = (coords: [number, number], name: string, id: string) => {
        let ol = this.olService.get();
        let iconFeature = new ol.Feature({
            geometry: new ol.geom.Point(ol.proj.transform(coords, 'EPSG:4326', 'EPSG:3857')),
            name: name,
            id: id,
        });

        let iconStyle = new ol.style.Style({
            image: new ol.style.Icon(/** @type {olx.style.IconOptions} */ ({
                opacity: 0.75,
                anchor: [0.5, 1],
                src: '//cdn4.iconfinder.com/data/icons/pictype-free-vector-icons/16/location-alt-32.png'
            }))
        });

        iconFeature.setStyle(iconStyle);

        this.vectorSource.addFeature(iconFeature);

    }

    addLayerSwitcher = (layers: [any]) => {

        this.layers = layers;

    }
    toggleLayer = (layer, evt) => {
        evt.target.blur();
        if (layer.getVisible()) {
            layer.setVisible(false);

        } else {
            layer.setVisible(true);
        }

    }

    ngOnInit() {
        this.createMap();
    }

}

回答1:

According to the given error stack, checked the related source code in ol.js, which explains what's happened for me:

The first error line:

at Kc (webpack:///~/openlayers/dist/ol.js:43:372 <- karma-shim.js:70246:398)

The snippet at ol.js:43:372:

function Kc(a,b,c){return Ic(b,c)(a,void 0,a.length)}

The second error line:

at Object.fromLonLat (webpack:///~/openlayers/dist/ol.js:768:188 <- karma-shim.js:70971:196)

The snippet at ol.js:768:188

r("ol.proj.fromLonLat",function(a,b){return Kc(a,"EPSG:4326",void 0!==b?b:"EPSG:3857")});

I suppose the invoking function Kc(a,b,c) missed parameter "a", which is a projection. Could you check if you have set the right projection before creating the map?