I'm getting a ctrlreg error when I try to open a modal dialog. I don't think it's a typo and I do think the controller is being registered, but when I attach to it in the debugger, I don't see the template file.
Here is the controller:
(function () {
angular.module('myAppName.controllers').controller('ConfirmationModalController', function ($scope, $uibModalInstance) {
$scope.del = function () {
$uibModalInstance.close(true);
};
$scope.keep = function () {
$uibModalInstance.close(false);
};
});
})();
That gets hit when I reload the page thus I'm guessing it's being registered.
Now here is how I attempt to create the dialog:
function deleteObject(obj) {
var modalInstance = $uibModal.open({
animation: true,
ariaLabelledBy: 'modal-title',
ariaDescribedBy: 'modal-body',
templateUrl: 'js/views/confirm_modal.tpl',
controller: 'ConfirmationModalController',
controllerAs: '$ctrl',
size: 'lg',
resolve: {
data: function () {
return $ctrl.data;
}
}
});
modalInstance.result.then(
function(obj)
{
$ctrl.data.objects = _.without($ctrl.data.objects, obj);
DataService.save($ctrl.data);
ObjectService.remove(object.id);
},
function () {
console.log("------------ERROR----------");
}
);
}
Edit:
Here is the error message I'm getting:
Error: [$controller:ctrlreg] http://errors.angularjs.org/1.6.2/$controller/ctrlreg?p0=ConfirmationModalController
at http://localhost:8080/webjars/angularjs/1.6.2/angular.min.js:6:425
at $get (http://localhost:8080/webjars/angularjs/1.6.2/angular.min.js:92:395)
at http://localhost:8080/webjars/angular-ui-bootstrap/2.2.0/ui-bootstrap-tpls.min.js:8:29619
at http://localhost:8080/webjars/angularjs/1.6.2/angular.min.js:134:167
at m.$get.m.$eval (http://localhost:8080/webjars/angularjs/1.6.2/angular.min.js:148:47)
at m.$get.m.$digest (http://localhost:8080/webjars/angularjs/1.6.2/angular.min.js:145:92)
at m.$get.m.$apply (http://localhost:8080/webjars/angularjs/1.6.2/angular.min.js:148:341)
at l (http://localhost:8080/webjars/angularjs/1.6.2/angular.min.js:101:89)
at XMLHttpRequest.t.onload (http://localhost:8080/webjars/angularjs/1.6.2/angular.min.js:106:489) Possibly unhandled rejection: {}
Related
I'm sitting with this question for a while.
I'm wondering how you can add different kind of controllers and different templateUrl from angularjs to $uibModal. So that You don't have that much code that actually does the same thing. Only on different controller and different template.
This is what I have done so far:
vm.addDialogue = function () {
vm.formData = {};
var modalInstance = $uibModal.open({
animation: true,
templateUrl: 'views/modals/add.dialogue.html',
controller: 'addDialogueController',
controllerAs: 'vm',
backdrop: 'static',
resolve: {
formData: function() {
return vm.formData;
},
selectedTab: 1,
editTime: false
}
});
};
vm.addProject = function(){
vm.formData = {};
var modalInstance = $uibModal.open({
animation: true,
templateUrl: 'views/modals/add.project.html',
controller: 'addProjectController',
controllerAs: 'vm',
backdrop: 'static',
resolve: {
formData: function () {
return vm.formData;
}
}
});
modalInstance.result.then(function (newProject) {
vm.errorMessage = null;
vm.projects.unshift(savedProject);
}, function () {
console.info('Modal dismissed at: ' + new Date());
});
}
This works but I have five more of these Modals, I'm trying to find something that these seven fit in one function and not seven different functions.
I have also tried putting this all in once controller instead of the controllers they belonged in. That worked for a moment but then it didn't showed some buttons that were supposed to be on a page.
Well, you could abstract your code by using a service and parse your params into. This service will handle the uibModal instance. In that way you could avoid your duplicate code.
AngularJS example application & service:
var myApp = angular.module('myApp', []);
//simple controller
myApp.controller('MyCtrl', function (myModalService) {
//open modal
myModalService.openModal('views/modals/add.project.html', 'addProjectController');
//close on click
$scope.close = function () {
myModalService.modalInstance.close();
}
});
// my simple modal service
myApp.service('myModalService', function() {
this.modalInstance = null;
this.openModal = function (templateUrl, controller) {
this.modalInstance = $uibModal.open({
animation: true,
templateUrl: templateUrl,
controller: controller,
controllerAs: 'vm',
backdrop: 'static',
resolve: {
formData: function() {
return vm.formData;
}
}
});
return this.modalInstance;
}
return this;
});
I have created a common ModalService and this is used for two diferrnt type of dialogs. CancelDialog and ErrorDialog will be popped up as per parameter passed to service.
Why do we Unit Test when functionality is working fine??
i.e This will show an ErrorDialog
ModalService.openModal('Analysis Error', 'I am Error Type', 'Error');
All is working fine but am stuck with Unit Test. Here is working PLUNKER.
Please help in covering Unit Test for this.
How to do Unit Test for openErrorModal & openCancelModal in below service.
ModalService
// common modal service
validationApp.service('ModalService',
function($uibModal) {
return {
openModal: openModal
};
function openErrorModal(title, message, callback) {
$uibModal.open({
templateUrl: 'ErrorDialog.html',
controller: 'ErrorDialogCtrl',
controllerAs: 'vm',
backdrop: 'static',
size: 'md',
resolve: {
message: function() {
return message;
},
title: function() {
return title;
},
callback: function() {
return callback;
}
}
});
}
function openCancelModal(title, message, callback) {
$uibModal.open({
templateUrl: 'CancelDialog.html',
controller: 'ErrorDialogCtrl',
controllerAs: 'vm',
backdrop: 'static',
size: 'md',
resolve: {
message: function() {
return message;
},
title: function() {
return title;
},
callback: function() {
return callback;
}
}
});
}
function openModal(title, message, modalType, callback) {
if (modalType === "Error") {
openErrorModal(title, message, callback);
} else {
openCancelModal(title, message, callback);
}
}
}
);
How to Unit Test onOk , onContinue & onDiscard in below controller.
DialogController
//controller fot dialog
validationApp.controller('ErrorDialogCtrl',
function($uibModalInstance, message, title, callback) {
alert('from controller');
var vm = this;
vm.message = message;
vm.onOk = onOk;
vm.onContinue = onContinue;
vm.onDiscard = onDiscard;
vm.callback = callback;
vm.title = title;
function onOk() {
$uibModalInstance.close();
}
function onContinue() {
$uibModalInstance.close();
}
function onDiscard() {
vm.callback();
$uibModalInstance.close();
}
});
You need to separately test service and controllers. For controllers, you need to test that methods of uibModalInstance are called when controller methods are called. You don't actually need to test that dialog closes, when close method is called. That is the task of those who implemented uibModal.
So here is the test for controller:
describe('ErrorDialogCtrl', function() {
// inject the module of your controller
beforeEach(module('app'));
var $controller;
beforeEach(inject(function(_$controller_){
// The injector unwraps the underscores (_) from around the parameter names when matching
$controller = _$controller_;
}));
it('tests that close method is called on modal dialog', function() {
var $uibModalInstance = {
close: jasmine.createSpy('close')
};
var callback = function() {};
var controller = $controller('PasswordController', { $uibModalInstance: $uibModalInstance, message: {}, callback: callback });
controller.onOk();
expect($uibModalInstance.close).toHaveBeenCalled();
});
});
Here is the simply test for service:
describe('ModalService', function () {
var $injector;
var $uibModal;
// inject the module of your controller
beforeEach(module('app', function($provide) {
$uibModal = {
open: jasmine.createSpy('open')
};
$provide.value('$uibModal', $uibModal);
}));
beforeEach(inject(function (_$injector_) {
$injector = _$injector_;
}));
it('tests that openErrorModal is called', function () {
var modalService = $injector.get('ModalService');
modalService.openModal(null, null, "Error");
expect($uibModal.open).toHaveBeenCalledWith(jasmine.objectContaining({
controller: "ErrorDialogCtrl"
}));
});
});
I use https://github.com/simpulton/angularjs-wizard for my project, it works,
(I modified it a little, replaced var app to $scope)
but I need to pass a variable to open function:
$scope.open = function (image)
{
$scope.image = image;
var modalInstance = $uibModal.open({
templateUrl: 'wizard.html',
controllerAs: 'modal',
size: 'lg',
controller: 'ModalCtrl',
resolve: {
image: function () {
return image;
}
}
});
modalInstance.result
.then(function (data) {
$scope.closeAlert();
$scope.summary = data;
}, function (reason) {
$scope.reason = reason;
});
};
and in html:
ng-click="open(image)"
but image is undefined in my template
it works if I only just the modal window, with the example from https://angular-ui.github.io/bootstrap/#/modal,
but not with this wizard example
update:
https://jsfiddle.net/Ginstay/znz64sk3/2/
yes, ajax is completed at the moment, when I open the modal window
and if I add a breakpoint to return image; image is there
Try this one
angular.module('plunker', ['ui.bootstrap']);
var ModalDemoCtrl = function ($scope, $modal) {
var modalInstance = $modal.open({
templateUrl: 'myModalContent.html',
controller: ModalInstanceCtrl,
resolve: {
test: function () {
return 'test variable';
}
}
});
};
var ModalInstanceCtrl = function ($scope, $modalInstance, test) {
$scope.test = test;
};
I figured it, I needed to add
$scope.image = image;
to ModalCtrl
I'm having problems trying to write a jasmine unit test for an Angular-Bootstrap $modal. The exact error is
Expected spy open to have been called with [ { templateUrl : '/n/views/consent.html', controller : 'W2ConsentModal as w2modal', resolve : { employee : Function }, size : 'lg' } ] but actual calls were [ { templateUrl : '/n/views/consent.html', controller : 'W2ConsentModal as w2modal', resolve : { employee : Function }, size : 'lg' } ]
The expected and actual modal options object are the same. What is going on?
Controller
(function () {
'use strict';
angular
.module('app')
.controller('W2History', W2History);
W2History.$inject = ['$scope', '$modal', 'w2Service'];
function W2History($scope, $modal, w2Service) {
/* jshint validthis:true */
var vm = this;
vm.showModal = showModal;
function showModal(employee) {
var modalInstance = $modal.open({
templateUrl: '/n/views/consent.html',
controller: 'W2ConsentModal as w2modal',
resolve: {
employee: function () {
return employee;
}
},
size: 'lg'
});
modalInstance.result.then(function (didConsent) {
// code omitted
});
}
}
})();
Test
describe('W2History controller', function () {
var controller, scope, modal;
var fakeModal = {
result: {
then: function (confirmCallback, cancelCallback) {
//Store the callbacks for later when the user clicks on the OK or Cancel button of the dialog
this.confirmCallBack = confirmCallback;
this.cancelCallback = cancelCallback;
}
},
close: function (item) {
//The user clicked OK on the modal dialog, call the stored confirm callback with the selected item
this.result.confirmCallBack(item);
},
dismiss: function (type) {
//The user clicked cancel on the modal dialog, call the stored cancel callback
this.result.cancelCallback(type);
}
};
var modalOptions = {
templateUrl: '/n/views/consent.html',
controller: 'W2ConsentModal as w2modal',
resolve: {
employee: function () {
return employee;
}
},
size: 'lg'
};
beforeEach(function () {
module('app');
inject(function (_$controller_, _$rootScope_, _$modal_) {
scope = _$rootScope_.$new();
modal = _$modal_;
spyOn(modal, 'open').and.returnValue(fakeModal);
controller = _$controller_('W2History', {
$scope: scope,
$modal: modal,
w2Service: w2Srvc
});
});
});
it('Should correctly show the W2 consent modal', function () {
var employee = terminatedaccessMocks.getCurrentUserInfo();
controller.showModal(employee);
expect(modal.open).toHaveBeenCalledWith(modalOptions);
});
});
Try this:
describe('W2History controller', function () {
var controller, scope, modal;
var fakeModal = {
result: {
then: function (confirmCallback, cancelCallback) {
//Store the callbacks for later when the user clicks on the OK or Cancel button of the dialog
this.confirmCallBack = confirmCallback;
this.cancelCallback = cancelCallback;
}
},
close: function (item) {
//The user clicked OK on the modal dialog, call the stored confirm callback with the selected item
this.result.confirmCallBack(item);
},
dismiss: function (type) {
//The user clicked cancel on the modal dialog, call the stored cancel callback
this.result.cancelCallback(type);
}
};
var modalOptions = {
templateUrl: '/n/views/consent.html',
controller: 'W2ConsentModal as w2modal',
resolve: {
employee: jasmine.any(Function)
},
size: 'lg'
};
var actualOptions;
beforeEach(function () {
module('plunker');
inject(function (_$controller_, _$rootScope_, _$modal_) {
scope = _$rootScope_.$new();
modal = _$modal_;
spyOn(modal, 'open').and.callFake(function(options){
actualOptions = options;
return fakeModal;
});
controller = _$controller_('W2History', {
$scope: scope,
$modal: modal
});
});
});
it('Should correctly show the W2 consent modal', function () {
var employee = { name : "test"};
controller.showModal(employee);
expect(modal.open).toHaveBeenCalledWith(modalOptions);
expect(actualOptions.resolve.employee()).toEqual(employee);
});
});
PLUNK
Explanation:
We should not expect the actual resolve.employee to be the same with the fake resolve.employee because resolve.employee is a function which returns an employee (in this case the employee is captured in closure). The function could be the same but at runtime the returned objects could be different.
The reason your test is failing is the way javascript compares functions. Take a look at this fiddle. Anyway, I don't care about this because we should not expect function implementations. What we do care about in this case is the resolve.employee returns the same object as we pass in:
expect(actualOptions.resolve.employee()).toEqual(employee);
So the solution here is:
We expect everything except for the resolve.employee:
var modalOptions = {
templateUrl: '/n/views/consent.html',
controller: 'W2ConsentModal as w2modal',
resolve: {
employee: jasmine.any(Function) //don't care about the function as we check it separately.
},
size: 'lg'
};
expect(modal.open).toHaveBeenCalledWith(modalOptions);
Check the resolve.employee separately by capturing it first:
var actualOptions;
spyOn(modal, 'open').and.callFake(function(options){
actualOptions = options; //capture the actual options
return fakeModal;
});
expect(actualOptions.resolve.employee()).toEqual(employee); //Check the returned employee is actually the one we pass in.
This is a pass by reference vs pass by value issue. The resolve.employee anonymous function used in $modal.open:
var modalInstance = $modal.open({
templateUrl: '/n/views/consent.html',
controller: 'W2ConsentModal as w2modal',
resolve: {
employee: function () {
return employee;
}
},
size: 'lg'
});
is not the same (by reference) as the resolve.employee anonymous function in your test:
var modalOptions = {
templateUrl: '/n/views/consent.html',
controller: 'W2ConsentModal as w2modal',
resolve: {
employee: function () {
return employee;
}
},
size: 'lg'
};
Your test should be:
resolve: {
employee: jasmine.any(Function)
}
If it's essential that the resolve function be tested, you should expose it somewhere where you can get a reference to the same function in your tests.
I am not sure if this will help you now, but when you spy on something you can get the argument that is passed to the $uibModal.open spy, you can then call that function to test that it returns what is in the resolve method.
it('expect resolve to be have metadataid that will return 9999', () => {
spyOn($uibModal, 'open');
//add test code here that will call the $uibModal.open
var spy = <jasmine.Spy>$uibModal.open;
var args = spy.calls.argsFor(0);
expect(args[0].resolve.metadataId()).toEqual(9999);
});
***** my code is using typescript, but this works for me.**
I have come across the same scenario. I have come across the problem with the below given solution
//Function to open export modal
scope.openExportModal();
expect( uibModal.open ).toHaveBeenCalledWith(options);
expect( uibModal.open.calls.mostRecent().args[0].resolve.modalData() ).toEqual(modalData);
Hope this may help if you want a quick fix.
I'm trying to write a unit test that asserts the correct variable is being sent to the resolve property of ui.bootstrap.modal from the Angular-UI Bootstrap components. Here is what I have so far:
// Controller
angular.module('app')
.controller('WorkflowListCtrl', function ($scope, $modal) {
// Setup the edit callback to open a modal
$scope.edit = function(name) {
var modalInstance = $modal.open({
templateUrl: 'partials/editWorkflowModal.html',
controller: 'WorkflowEditCtrl',
scope: $scope,
resolve: {
name: function() { return name; }
}
});
};
});
It's worth noting that the resolve.name property must be a function for the Angular-UI component to work correctly - previously I had tried resolve: { name: name } but this didn't work.
// Unit Test
describe('Controller: WorkflowListCtrl', function () {
// load the controller's module
beforeEach(module('app'));
var workflowListCtrl,
scope,
modal;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
modal = {
open: jasmine.createSpy()
};
workflowListCtrl = $controller('WorkflowListCtrl', {
$scope: scope,
$modal: modal
});
it('should allow a workflow to be edited', function() {
// Edit workflow happens in a modal.
scope.edit('Barney Rubble');
expect(modal.open).toHaveBeenCalledWith({
templateUrl: 'partials/editWorkflowModal.html',
controller: 'WorkflowEditCtrl',
scope: scope,
resolve: {
name: jasmine.any(Function)
}
});
});
}));
});
At the moment, this is just checking that the resolve.name property is a function, but what I'd really like to do is assert the resolve.name function returns Barney Rubble. This syntax obviously doesn't work:
expect(modal.open).toHaveBeenCalledWith({
templateUrl: 'partials/editWorkflowModal.html',
controller: 'WorkflowEditCtrl',
scope: scope,
resolve: {
name: function() { return 'Barney Rubble'; }
}
});
It seems like I somehow want to spy on the resolve.name function to check it was called with Barney Rubble but I can't figure out a way to do that. Any ideas?
So I have figured out a way to do this.
Define a 'private' function on $scope:
$scope._resolve = function(item) {
return function() {
return item;
};
};
Modify the original $scope function to call this 'private' method:
$scope.edit = function(name) {
var modalInstance = $modal.open({
templateUrl: 'partials/modal.html',
controller: 'ModalCtrl',
scope: $scope,
resolve: {
name: $scope._resolve(name)
}
});
};
Update your tests to mock this function and return the original value, then you can test it was passed in correctly.
it('should allow a workflow to be edited', function() {
// Mock out the resolve fn and return our item
spyOn($scope, '_resolve').and.callFake(function(item) {
return item;
});
// Edit workflow happens in a modal.
scope.edit('Barney Rubble');
expect(modal.open).toHaveBeenCalledWith({
templateUrl: 'partials/modal.html',
controller: 'ModalCtrl',
scope: scope,
resolve: {
name: 'Barney Rubble'
}
});
});