codeepic
codeepic

Reputation: 4102

Angular modal controller testing error

I have a hard time trying to test a modal controller (created using Angular UI Bootstrap). I dumbed down the test code as much as I could but I am still getting an error. Here's the modal controller (part of it):

var controllersModule = angular.module('forge.geomanagement.controllers');

controllersModule.controller('EditGeofenceModalController', function ($timeout, $scope: , $modalInstance, forgeGeoTriggerService, $rootScope, geofence, triggerID) {

  var searchAddressInput: HTMLInputElement;
  //make a copy of geofence obj passed into modal
  $scope.geofence = {
    FriendlyName: geofence.FriendlyName,
    Coords: angular.copy(geofence.Boundary),
    GeoTags: angular.copy(geofence.GeoTags)
  };
  $scope.goefenceID = triggerID;

  var gCLength = $scope.geofence.Coords.length;

  //wrap it in timeout function to paint the map after its container is rendered
  $timeout(function () { 

    $scope.geofenceMap = new google.maps.Map(document.getElementById('map_canvas'),  $scope.mapOptions);
    //autocomplete functionality
    searchAddressInput = <HTMLInputElement>document.getElementById('pac-input');
    $scope.autocomplete = new google.maps.places.Autocomplete(searchAddressInput, $scope.mapOptions);
    $scope.autocomplete.bindTo('bounds', $scope.geofenceMap); //set autocomplete suggestion bounds to map's current viewport

    //bind autocomplete to the map
    google.maps.event.addListener($scope.autocomplete, 'place_changed', function () {
        $scope.place = $scope.autocomplete.getPlace();
        $scope.geofenceMap.panTo($scope.place.geometry.location);
        $scope.geofenceMap.setZoom(12);
        $scope.model.searchAddress = $scope.place.formatted_address;
        $scope.$digest();
    });

    //GEOFENCE FUNCTIONALITY
    forgeGeoTriggerService.GeofenceCreator($scope.geofenceMap, $scope.geofence.Coords);

    //show geofence in edit mode
    forgeGeoTriggerService.ShowGeofence($scope.geofenceMap, $scope.geofence.Coords);

    $scope.$on("polygonPath.updated", function (event, geofenceCoords) {
        $scope.$apply(function () {
            $scope.geofence.Coords = geofenceCoords;
        });
    });

    //clear geofence area btn
    $scope.clearGeofenceArea = function () {
        forgeGeoTriggerService.ClearGeofenceArea();
        $scope.geofence.Coords.length = 0; // clear geofence array
    };

}, 0);

$scope.cancel = function () {
    $modalInstance.close()
};

$scope.saveGeofence = function () {
    forgeGeoTriggerService.EditGeofence($scope.geofence, $scope.goefenceID)
        .then(function (data) {
            $scope.successMessage = 'Geofence Updated Successfully'
            $rootScope.$broadcast('geotrigger.edited');
            $timeout(function () {
                $modalInstance.close();
            }, 2000);
        }, function (data) {
            $scope.errorMessage = 'There was an error when updating geofence. Please try again.';
        });
}

});

This is modal controller test

describe("forge.geomanagement.GeoApp", function () {

var scope, controller, modalInstance, timeout, forgeGeoTriggerService, window = {},
    geofencemock, geofence, triggerID;

beforeEach(module('forge.geomanagement.GeoApp'));

describe("Controller: EditGeofenceModalController", function () {

    beforeEach(inject(function ($controller, $rootScope, $timeout, _forgeGeoTriggerService_) {
        scope = $rootScope.$new();
        timeout = $timeout;

        modalInstance = { 
            close: jasmine.createSpy('modalInstance.close'),
            dismiss: jasmine.createSpy('modalInstance.dismiss'),
            result: {
                then: jasmine.createSpy('modalInstance.result.then')
            }
        }

        geofencemock = {
            FriendlyName: 'mock geofence',
            Coords: [
                {
                    "lat": 53.5598889724547,
                    "lng": -6.36953830718994
                },
                {
                    "lat": 53.463525599115,
                    "lng": -6.53707981109619
                },
                {
                    "lat": 53.3685818160803,
                    "lng": -6.46841526031494
                },
                {
                    "lat": 53.384966558115,
                    "lng": -5.75430393218994
                },
                {
                    "lat": 53.5598889724547,
                    "lng": -6.34756565093994
                },
                {
                    "lat": 53.5598889724547,
                    "lng": -6.36953830718994
                }
            ],
            GeoTags: ['tag1','tag2','tag3']
        }

        triggerIDmock = 1;

        forgeGeoTriggerService = _forgeGeoTriggerService_;

        controller = $controller("EditGeofenceModalController", {
            $scope: scope,
            $timeout: timeout,
            $modalInstance: modalInstance,
            forgeGeoTriggerService: forgeGeoTriggerService,
            geofence: geofencemock,
            triggerID: triggerIDmock
        });
    }));

    it('2 is 2', function () {
        expect(2).toBe(2);
    })

    it("geofence should be defined", function () {
        expect(geofencemock).toBeDefined();
    });

    it("should contain reference to forgeGeoTriggerService", function () {
        expect(forgeGeoTriggerService).not.toBeNull();
    });

    it("$modalInstance obj should be defined when modal is open", function () {
        expect(modalInstance).toBeDefined();
    });

    it("cancel function should close edit geofence modal", function () {
        scope.cancel();
        expect(modalInstance.close).toHaveBeenCalled();
    });

});

});

But when I try to run it I get the error: "Cannot read property length of undefined" that corresponds to $scope.geofence.Coords property - an array that is successfully copied over to modal from parent controller. As you can see, I also created a geofencemock object and tried to use it in a very simple test but it looks like it's not being picked up. I would really appreciate some input, cause I have already spent couple of hours trying to fix it or find a solution online, but to no avail.

Thanks.

Upvotes: 0

Views: 400

Answers (2)

codeepic
codeepic

Reputation: 4102

Ok, I got it working. The error "Cannot read property 'lat' of undefined" was related not to the coordinates in geofence mock object but to the geofence.Center.lat property I was using in my controller with geofence.Center.lng to position the center of the map. Let me explain: we get the polygon details from the server, then we pass them into edit modal window (Angular UI Bootstrap):

forgeGeoTriggerService.GetGeoFence(geotriggerID)
                .then(function (geofenceData) {
                    $scope.modalInstance = $modal.open({
                        windowClass: 'popup-geofence-modal',
                        templateUrl: TemplateUrlProvider.GetUrl('GeofenceModal'),
                        controller: 'EditGeofenceModalController',
                        resolve: {//pass geofenceData from server to geofence obj inside modal
                            geofence: function () {
                                return geofenceData;
                            },
                            triggerID: function () {
                                return geotriggerID
                            }
                        }
                    });
                }, function (error) {
                    $scope.errorMessage = 'There was an error when trying to fetch geofencene details. Please try again later.';
                });

Then in EditGeofenceModalController we make use of the geofence object passed from the parent controller above

'use strict';
var controllersModule = angular.module('forge.geomanagement.controllers');

controllersModule.controller('EditGeofenceModalController', function ($timeout, $scope, $modalInstance, forgeGeoTriggerService, $rootScope, geofence, triggerID) {
var searchAddressInput: HTMLInputElement;

//make a copy of geofence obj passed into modal
$scope.geofence = {
    FriendlyName: geofence.FriendlyName,
    Coords: angular.copy(geofence.Boundary),
    GeoTags: angular.copy(geofence.GeoTags)
};
$scope.goefenceID = triggerID;

var gCLength = $scope.geofence.Coords.length;
//if first and last coords are the same - remove the last one
if ($scope.geofence.Coords[0].lat === $scope.geofence.Coords[gCLength - 1].lat
    && $scope.geofence.Coords[0].lng === $scope.geofence.Coords[gCLength - 1].lng) {
        $scope.geofence.Coords.pop();
}

//!!!!!!!set the map center to geofence.Center
var geofenceCenter: google.maps.LatLng = new google.maps.LatLng(
    geofence.Center.lat, geofence.Center.lng
    );

Pay attention to the comment line with exclamation marks. This is where I set the center of the map. The geofence object returned from the server has a Center property - an obj with lat and lng properties. Once I changed the Coords to Boundary in my geofencemock obj in test as @rayners suggested, it was still missing the Center property. Setting it like that in the test file fixed the problem and my tests passed:

geofencemock = {
            FriendlyName: 'mock geofence',
            Boundary: [
                {
                    "lat": 53.5598889724547,
                    "lng": -6.36953830718994
                },
                {
                    "lat": 53.463525599115,
                    "lng": -6.53707981109619
                },
                {
                    "lat": 53.3685818160803,
                    "lng": -6.46841526031494
                },
                {
                    "lat": 53.384966558115,
                    "lng": -5.75430393218994
                },
                {
                    "lat": 53.5598889724547,
                    "lng": -6.34756565093994
                },
                {
                    "lat": 53.5598889724547,
                    "lng": -6.36953830718994
                }
            ],
            GeoTags: ['tag1', 'tag2', 'tag3'],
            Center: {
                "lat": 53.46769593973309,
                "lng": -6.2952017905716735
            }
        }

Upvotes: 0

rayners
rayners

Reputation: 539

You're setting $scope.geofence.Coords from geofence.Boundary:

$scope.geofence = {
  FriendlyName: geofence.FriendlyName,
  Coords: angular.copy(geofence.Boundary),
  GeoTags: angular.copy(geofence.GeoTags)
};

But you're mocking geofence with Coords directly:

geofencemock = {
  FriendlyName: 'mock geofence',
  Coords: [
    {
      "lat": 53.5598889724547,
      "lng": -6.36953830718994
    },

Change the latter to be geofencemock.Boundary and you should be fine.

Upvotes: 1

Related Questions