src/modal/test/modal.spec.js
'use strict';
describe('modal', function() {
var bodyEl = $('body'), sandboxEl;
var $rootScope, $compile, $templateCache, $$rAF, $animate, $timeout, $httpBackend, $modal, scope;
beforeEach(module('ngSanitize'));
beforeEach(module('ngAnimate'));
beforeEach(module('ngAnimateMock'));
beforeEach(module('mgcrea.ngStrap.modal'));
beforeEach(module(function($controllerProvider) {
$controllerProvider.register('MyModalController', function($scope) {
$scope.title = 'foo';
$scope.content = 'bar';
});
}));
beforeEach(inject(function($injector) {
$rootScope = $injector.get('$rootScope');
$compile = $injector.get('$compile');
$templateCache = $injector.get('$templateCache');
$$rAF = $injector.get('$$rAF');
$animate = $injector.get('$animate');
$timeout = $injector.get('$timeout');
var flush = $animate.flush || $animate.triggerCallbacks;
$animate.flush = function() {
flush.call($animate); if(!$animate.triggerCallbacks) $timeout.flush();
};
$httpBackend = $injector.get('$httpBackend');
$modal = $injector.get('$modal');
bodyEl.html('');
sandboxEl = $('<div>').attr('id', 'sandbox').appendTo(bodyEl);
scope = $rootScope.$new();
}));
afterEach(function() {
scope.$destroy();
sandboxEl.remove();
});
// Templates
var templates = {
'default': {
scope: {modal: {title: 'Title', content: 'Hello Modal!'}},
element: '<a title="{{modal.title}}" data-content="{{modal.content}}" bs-modal>click me</a>'
},
'default-with-namespace': {
scope: {modal: {title: 'Title', content: 'Hello Modal!'}},
element: '<a title="{{modal.title}}" data-content="{{modal.content}}" bs-modal data-prefix-event="datepicker">click me</a>'
},
'default-with-id': {
scope: {modal: {title: 'Title', content: 'Hello Modal!'}},
element: '<a id="modal1" title="{{modal.title}}" data-content="{{modal.content}}" bs-modal>click me</a>'
},
'markup-scope': {
element: '<a bs-modal="modal">click me</a>'
},
'markup-ngRepeat': {
scope: {items: [{name: 'foo', modal: {title: 'Title', content: 'Hello Modal!'}}]},
element: '<ul><li ng-repeat="item in items"><a title="{{item.modal.title}}" data-content="{{item.modal.content}}" bs-modal>{{item.name}}</a></li></ul>'
},
'markup-ngClick-service': {
element: '<a ng-click="showModal()">click me</a>'
},
'options-controller': {
element: '<a data-controller="MyModalController" bs-modal>click me</a>'
},
'options-placement': {
element: '<a data-placement="bottom" bs-modal="modal">click me</a>'
},
'options-placement-exotic': {
element: '<a data-placement="center" bs-modal="modal">click me</a>'
},
'options-html': {
scope: {modal: {title: 'title<br>next', content: 'content<br>next'}},
element: '<a title="{{modal.title}}" data-content="{{modal.content}}" data-html="{{html}}" bs-modal>click me</a>'
},
'options-backdrop': {
element: '<a bs-modal="modal" data-backdrop="{{backdrop}}">click me</a>'
},
'options-keyboard': {
element: '<a bs-modal="modal" data-keyboard="{{keyboard}}">click me</a>'
},
'options-container': {
element: '<a bs-modal="modal" data-container="{{container}}">click me</a>'
},
'options-template': {
scope: {modal: {title: 'Title', content: 'Hello Modal!', counter: 0}, items: ['foo', 'bar', 'baz']},
element: '<a title="{{modal.title}}" data-content="{{modal.content}}" data-template-url="custom" bs-modal>click me</a>'
},
'options-contentTemplate': {
scope: {modal: {title: 'Title', content: 'Hello Modal!', counter: 0}, items: ['foo', 'bar', 'baz']},
element: '<a title="{{modal.title}}" data-content="{{modal.content}}" data-content-template="custom" bs-modal>click me</a>'
},
'options-modalClass': {
element: '<a bs-modal="modal" data-modal-class="my-custom-class">click me</a>'
},
'options-size-lg': {
element: '<a bs-modal="modal" data-size="lg">click me</a>'
},
'options-size-invalid': {
element: '<a bs-modal="modal" data-size="md">click me</a>'
},
'options-events': {
element: '<a bs-on-before-hide="onBeforeHide" bs-on-hide="onHide" bs-on-before-show="onBeforeShow" bs-on-show="onShow" bs-modal="modal">click me</a>'
},
'options-z-index': {
element: '<a bs-modal="modal" data-z-index="{{zIndex}}">click me</a>'
},
'508': {
scope: {modal: {title: 'Title', content: 'Hello Modal!'}},
element: '<button type="button" class="btn btn-primary" bs-modal="modal" keyboard="true" backdrop="static">Click Me</button>'
}
};
function compileDirective(template, locals) {
template = templates[template];
angular.extend(scope, template.scope || templates['default'].scope, locals);
var element = $(template.element).appendTo(sandboxEl);
element = $compile(element)(scope);
scope.$digest();
return jQuery(element[0]);
}
// Tests
describe('with default template', function() {
it('should open on click', function() {
var elm = compileDirective('default');
expect(sandboxEl.children('.modal').length).toBe(0);
angular.element(elm[0]).triggerHandler('click');
expect(sandboxEl.children('.modal').length).toBe(1);
});
it('should close on click', function() {
var elm = compileDirective('default');
expect(sandboxEl.children('.modal').length).toBe(0);
angular.element(elm[0]).triggerHandler('click');
angular.element(elm[0]).triggerHandler('click');
expect(sandboxEl.children('.modal').length).toBe(0);
});
it('should correctly compile inner content', function() {
var elm = compileDirective('default');
angular.element(elm[0]).triggerHandler('click');
expect(sandboxEl.find('.modal-title').html()).toBe(scope.modal.title);
expect(sandboxEl.find('.modal-body').html()).toBe(scope.modal.content);
});
it('should support scope as object', function() {
var elm = compileDirective('markup-scope');
angular.element(elm[0]).triggerHandler('click');
expect(sandboxEl.find('.modal-title').html()).toBe(scope.modal.title);
expect(sandboxEl.find('.modal-body').html()).toBe(scope.modal.content);
});
it('should support ngRepeat markup inside', function() {
var elm = compileDirective('markup-ngRepeat');
angular.element(elm.find('[bs-modal]')).triggerHandler('click');
expect(sandboxEl.find('.modal-title').html()).toBe(scope.items[0].modal.title);
expect(sandboxEl.find('.modal-body').html()).toBe(scope.items[0].modal.content);
});
});
describe('using service', function() {
it('should correctly open on next digest', function() {
var myModal = $modal(templates['default'].scope.modal);
scope.$digest();
expect(bodyEl.children('.modal').length).toBe(1);
myModal.hide();
expect(bodyEl.children('.modal').length).toBe(0);
});
it('should correctly be destroyed', function() {
var myModal = $modal(angular.extend(templates['default'].scope.modal));
scope.$digest();
expect(bodyEl.children('.modal').length).toBe(1);
myModal.destroy();
expect(bodyEl.children('.modal').length).toBe(0);
expect(bodyEl.children().length).toBe(1);
});
it('should correctly work with ngClick', function() {
var elm = compileDirective('markup-ngClick-service');
var myModal = $modal(angular.extend({show: false}, templates['default'].scope.modal));
scope.showModal = function() {
myModal.$promise.then(myModal.show);
};
expect(bodyEl.children('.modal').length).toBe(0);
angular.element(elm[0]).triggerHandler('click');
expect(bodyEl.children('.modal').length).toBe(1);
});
it('should correctly work with ngClick with an isolated scope', function() {
scope = scope.$new(true);
var elm = compileDirective('markup-ngClick-service');
var myModal = $modal(angular.extend({show: false, scope: scope}, templates['default'].scope.modal));
scope.showModal = function() {
myModal.$promise.then(myModal.show);
};
expect(bodyEl.children('.modal').length).toBe(0);
angular.element(elm[0]).triggerHandler('click');
expect(bodyEl.children('.modal').length).toBe(1);
});
it('should store config id value in instance', function() {
var myModal = $modal({ title: 'Title', content: 'Hello Modal!', id: 'modal1' });
expect(myModal.$id).toBe('modal1');
});
it('should fallback to element id value when id is not provided in config', function() {
var myModal = $modal({ title: 'Title', content: 'Hello Modal!', element: sandboxEl });
expect(myModal.$id).toBe('sandbox');
});
});
describe('using scope helpers', function() {
var elm, elmScope;
beforeEach(function() {
elm = compileDirective('default');
elmScope = angular.element(elm).scope().$$childTail;
scope.$digest();
});
it('should correctly open on next digest', function() {
expect(sandboxEl.children('.modal').length).toBe(0);
expect(elmScope.$isShown).toBeFalsy();
elmScope.$show();
try { $animate.flush(); } catch(err) {}
scope.$digest();
expect(sandboxEl.children('.modal').length).toBe(1);
expect(elmScope.$isShown).toBeTruthy();
elmScope.$hide();
$animate.flush();
scope.$digest();
expect(sandboxEl.children('.modal').length).toBe(0);
expect(elmScope.$isShown).toBeFalsy();
elmScope.$toggle();
$animate.flush();
scope.$digest();
expect(sandboxEl.children('.modal').length).toBe(1);
expect(elmScope.$isShown).toBeTruthy();
elmScope.$toggle();
$animate.flush();
scope.$digest();
expect(sandboxEl.children('.tooltip').length).toBe(0);
expect(elmScope.$isShown).toBeFalsy();
});
it('should do nothing when hiding an already hidden popup', function() {
expect(sandboxEl.children('.modal').length).toBe(0);
expect(elmScope.$isShown).toBeFalsy();
elmScope.$hide();
try { $animate.flush(); } catch(err) {}
scope.$digest();
expect(sandboxEl.children('.modal').length).toBe(0);
expect(elmScope.$isShown).toBeFalsy();
});
it('should do nothing when showing an already visible popup', function() {
expect(sandboxEl.children('.modal').length).toBe(0);
expect(elmScope.$isShown).toBeFalsy();
elmScope.$show();
try { $animate.flush(); } catch(err) {}
scope.$digest();
expect(sandboxEl.children('.modal').length).toBe(1);
expect(elmScope.$isShown).toBeTruthy();
elmScope.$show();
scope.$digest();
expect(sandboxEl.children('.modal').length).toBe(1);
expect(elmScope.$isShown).toBeTruthy();
});
});
describe('show / hide events', function() {
it('should dispatch show and show.before events', function() {
var myModal = $modal(templates['default'].scope.modal);
var emit = spyOn(myModal.$scope, '$emit').and.callThrough();
scope.$digest();
expect(emit).toHaveBeenCalledWith('modal.show.before', myModal);
// show only fires AFTER the animation is complete
expect(emit).not.toHaveBeenCalledWith('modal.show', myModal);
$animate.flush();
expect(emit).toHaveBeenCalledWith('modal.show', myModal);
});
it('should dispatch hide and hide.before events', function() {
var myModal = $modal(templates['default'].scope.modal);
scope.$digest();
var emit = spyOn(myModal.$scope, '$emit').and.callThrough();
myModal.hide();
expect(emit).toHaveBeenCalledWith('modal.hide.before', myModal);
// hide only fires AFTER the animation is complete
expect(emit).not.toHaveBeenCalledWith('modal.hide', myModal);
$animate.flush();
expect(emit).toHaveBeenCalledWith('modal.hide', myModal);
});
it('should namespace show/hide events using the prefixEvent', function() {
var myModal = $modal(angular.extend({prefixEvent: 'alert'}, templates['default'].scope.modal));
var emit = spyOn(myModal.$scope, '$emit').and.callThrough();
scope.$digest();
myModal.hide();
$animate.flush();
expect(emit).toHaveBeenCalledWith('alert.show.before', myModal);
expect(emit).toHaveBeenCalledWith('alert.show', myModal);
expect(emit).toHaveBeenCalledWith('alert.hide.before', myModal);
expect(emit).toHaveBeenCalledWith('alert.hide', myModal);
});
it('should can cancel show on show.before event', function() {
$rootScope.$on('modal.show.before', function(e) {
e.preventDefault();
});
$rootScope.$on('modal.show', function() {
throw new Error('modal should not be shown');
});
var myModal = $modal(templates['default'].scope.modal);
scope.$digest();
try { $animate.flush(); } catch(err) {}
});
it('should be able to cancel hide on hide.before event', function() {
$rootScope.$on('modal.hide.before', function(e) {
e.preventDefault();
});
$rootScope.$on('modal.hide', function() {
throw new Error('modal should not be hidden');
});
var myModal = $modal(templates['default'].scope.modal);
scope.$digest();
myModal.hide();
$animate.flush();
});
it('should call show.before event with modal element instance id', function() {
var elm = compileDirective('default-with-id');
var id = "";
scope.$on('modal.show.before', function(evt, modal) {
id = modal.$id;
});
angular.element(elm[0]).triggerHandler('click');
scope.$digest();
expect(id).toBe('modal1');
});
it('should call namespaced events through directive', function() {
var elm = compileDirective('default-with-namespace');
var showBefore, show, hide, hideBefore;
scope.$on('datepicker.show.before', function() {
showBefore = true;
});
scope.$on('datepicker.show', function() {
show = true;
});
scope.$on('datepicker.hide.before', function() {
hideBefore = true;
});
scope.$on('datepicker.hide', function() {
hide = true;
});
angular.element(elm[0]).triggerHandler('click');
$animate.flush();
expect(showBefore).toBe(true);
expect(show).toBe(true);
angular.element(elm[0]).triggerHandler('click');
$animate.flush();
expect(hideBefore).toBe(true);
expect(hide).toBe(true);
});
});
describe('options', function() {
describe('animation', function() {
it('should default to `am-fade` animation', function() {
var elm = compileDirective('default');
angular.element(elm[0]).triggerHandler('click');
expect(sandboxEl.children('.modal')).toHaveClass('am-fade');
});
});
describe('keyboard', function() {
it('should dismiss and stopPropagation if ESC is pressed', function() {
var myModal = $modal(templates['default'].scope.modal);
scope.$digest();
expect(bodyEl.children('.modal').length).toBe(1);
var evt = jQuery.Event( 'keyup', { keyCode: 27, which: 27 } );
spyOn(evt, 'stopPropagation');
myModal.$onKeyUp(evt);
expect(bodyEl.children('.modal').length).toBe(0);
expect(evt.stopPropagation).toHaveBeenCalled();
});
it('should NOT stopPropagation if ESC is pressed while modal is hidden', function() {
var myModal = $modal(templates['default'].scope.modal);
scope.$digest();
myModal.hide();
var evt = jQuery.Event( 'keyup', { keyCode: 27, which: 27 } );
spyOn(evt, 'stopPropagation');
myModal.$onKeyUp(evt);
expect(evt.stopPropagation).not.toHaveBeenCalled();
});
// Note: modal.trigger(evt) does not trigger modal keyup handler, only modal.triggerHandler(evt) does
it('should remove modal when data-keyboard is truthy', function() {
var elm = compileDirective('options-keyboard', {keyboard: 'true'});
expect(bodyEl.find('.modal').length).toBe(0);
angular.element(elm[0]).triggerHandler('click');
var modal = bodyEl.find('.modal');
expect(modal.length).toBe(1);
var evt = jQuery.Event( 'keyup', { keyCode: 27, which: 27 } );
modal.triggerHandler(evt)
expect(bodyEl.find('.modal').length).toBe(0);
});
it('should NOT remove modal when data-keyboard is falsy', function() {
var elm = compileDirective('options-keyboard', {keyboard: 'false'});
expect(bodyEl.find('.modal').length).toBe(0);
angular.element(elm[0]).triggerHandler('click');
var modal = bodyEl.find('.modal');
expect(modal.length).toBe(1);
var evt = jQuery.Event( 'keyup', { keyCode: 27, which: 27 } );
modal.triggerHandler(evt)
expect(bodyEl.find('.modal').length).toBe(1);
});
});
describe('controller', function() {
it('should properly invoke our passed controller', function() {
var elm = compileDirective('options-controller');
angular.element(elm[0]).triggerHandler('click');
expect(sandboxEl.find('.modal-title').html()).toBe('foo');
expect(sandboxEl.find('.modal-body').html()).toBe('bar');
});
});
describe('placement', function() {
it('should default to `top` placement', function() {
var elm = compileDirective('default');
angular.element(elm[0]).triggerHandler('click');
expect(sandboxEl.children('.modal')).toHaveClass('top');
});
it('should support placement', function() {
var elm = compileDirective('options-placement');
angular.element(elm[0]).triggerHandler('click');
expect(sandboxEl.children('.modal')).toHaveClass('bottom');
});
it('should support exotic-placement', function() {
var elm = compileDirective('options-placement-exotic');
angular.element(elm[0]).triggerHandler('click');
expect(sandboxEl.children('.modal')).toHaveClass('center');
});
});
describe('html', function() {
it('should NOT compile inner content by default', function() {
var elm = compileDirective('default', {modal: {title: 'title<br>next', content: 'content<br>next</span>'}});
angular.element(elm[0]).triggerHandler('click');
expect(sandboxEl.find('.modal-title').html()).not.toBe('title<br>next');
expect(sandboxEl.find('.modal-body').html()).not.toBe('content<br>next');
});
it('should compile inner content if html is true', function() {
var elm = compileDirective('options-html', {html: true});
angular.element(elm[0]).triggerHandler('click');
expect(sandboxEl.find('.modal-title').html()).toBe('title<br>next');
expect(sandboxEl.find('.modal-body').html()).toBe('content<br>next');
});
it('should compile inner content if html is truthy', function() {
var elm = compileDirective('options-html', {html: 'true'});
angular.element(elm[0]).triggerHandler('click');
expect(sandboxEl.find('.modal-title').html()).toBe('title<br>next');
expect(sandboxEl.find('.modal-body').html()).toBe('content<br>next');
});
// we'll test all permutations of falsy here ('False', 0, ''). They all use the same regex, so once should suffice
it('should NOT compile inner content if html is false', function() {
var elm = compileDirective('options-html', {html: false});
angular.element(elm[0]).triggerHandler('click');
expect(sandboxEl.find('.modal-title').html()).not.toBe('title<br>next');
expect(sandboxEl.find('.modal-body').html()).not.toBe('content<br>next');
});
it('should NOT compile inner content if html is False', function() {
var elm = compileDirective('options-html', {html: 'False'});
angular.element(elm[0]).triggerHandler('click');
expect(sandboxEl.find('.modal-title').html()).not.toBe('title<br>next');
expect(sandboxEl.find('.modal-body').html()).not.toBe('content<br>next');
});
it('should NOT compile inner content if html is 0', function() {
var elm = compileDirective('options-html', {html: '0'});
angular.element(elm[0]).triggerHandler('click');
expect(sandboxEl.find('.modal-title').html()).not.toBe('title<br>next');
expect(sandboxEl.find('.modal-body').html()).not.toBe('content<br>next');
});
it('should NOT compile inner content if html is empty string', function() {
var elm = compileDirective('options-html', {html: ''});
angular.element(elm[0]).triggerHandler('click');
expect(sandboxEl.find('.modal-title').html()).not.toBe('title<br>next');
expect(sandboxEl.find('.modal-body').html()).not.toBe('content<br>next');
});
});
describe('template', function() {
it('should support custom template', function() {
$templateCache.put('custom', '<div class="modal"><div class="modal-inner">foo: {{title}}</div></div>');
var elm = compileDirective('options-template');
angular.element(elm[0]).triggerHandler('click');
expect(sandboxEl.find('.modal-inner').text()).toBe('foo: ' + scope.modal.title);
});
it('should support custom template loaded by ngInclude', function() {
$templateCache.put('custom', [200, '<div class="modal"><div class="modal-inner">foo: {{title}}</div></div>', {}, 'OK']);
var elm = compileDirective('options-template');
angular.element(elm[0]).triggerHandler('click');
expect(sandboxEl.find('.modal-inner').text()).toBe('foo: ' + scope.modal.title);
});
it('should request custom template via $http', function() {
$httpBackend.expectGET('custom').respond(200, '<div class="modal"><div class="modal-inner">foo: {{title}}</div></div>');
var elm = compileDirective('options-template');
$httpBackend.flush();
angular.element(elm[0]).triggerHandler('click');
expect(sandboxEl.find('.modal-inner').text()).toBe('foo: ' + scope.modal.title);
});
it('should request custom template via $http only once', function() {
$httpBackend.expectGET('custom').respond(200, '<div class="modal"><div class="modal-inner">foo: {{title}}</div></div>');
var elm = compileDirective('options-template');
var elmBis = compileDirective('options-template');
$httpBackend.flush();
angular.element(elm[0]).triggerHandler('click');
expect(sandboxEl.find('.modal-inner').text()).toBe('foo: ' + scope.modal.title);
});
it('should support template with ngRepeat', function() {
$templateCache.put('custom', '<div class="modal"><div class="modal-inner"><ul><li ng-repeat="item in items">{{item}}</li></ul></div></div>');
var elm = compileDirective('options-template');
angular.element(elm[0]).triggerHandler('click');
expect(sandboxEl.find('.modal-inner').text()).toBe('foobarbaz');
// Consecutive toggles
angular.element(elm[0]).triggerHandler('click');
angular.element(elm[0]).triggerHandler('click');
expect(sandboxEl.find('.modal-inner').text()).toBe('foobarbaz');
});
it('should support template with ngClick', function() {
$templateCache.put('custom', '<div class="modal"><div class="modal-inner"><a class="btn" ng-click="modal.counter=modal.counter+1">click me</a></div></div>');
var elm = compileDirective('options-template');
angular.element(elm[0]).triggerHandler('click');
expect(angular.element(sandboxEl.find('.modal-inner > .btn')[0]).triggerHandler('click'));
expect(scope.modal.counter).toBe(1);
// Consecutive toggles
angular.element(elm[0]).triggerHandler('click');
angular.element(elm[0]).triggerHandler('click');
expect(angular.element(sandboxEl.find('.modal-inner > .btn')[0]).triggerHandler('click'));
expect(scope.modal.counter).toBe(2);
});
it('should destroy inner scopes when hidding modal', function() {
var scopeCount = countScopes(scope, 0);
var originalScope = scope;
scope = scope.$new();
$templateCache.put('custom', '<div class="modal"><div class="modal-inner"><div ng-if="1===1">Fake element to force creation of a new $scope</div><div class="btn" ng-click="$hide()"></div></div></div>');
var elm = compileDirective('options-template');
// We are only destroying the modal element before showing another
// modal. This is to avoid timming issues with the hide animation
// callback, because we could be showing a new modal before the
// hide animation callback has been called and then the modal element
// variables would be replaced with the new modal.
// So, for this test to work, we need to show/hide the modal once
// before counting the number of scopes expected.
angular.element(elm[0]).triggerHandler('click');
expect(angular.element(sandboxEl.find('.modal-inner > .btn')[0]).triggerHandler('click'));
// repeat process to test creation/destruction of inner scopes
var scopeCountAfterShow = countScopes(scope, 0);
for (var i = 0; i < 10; i++) {
// show modal
angular.element(elm[0]).triggerHandler('click');
// hide modal
expect(angular.element(sandboxEl.find('.modal-inner > .btn')[0]).triggerHandler('click'));
}
// scope count should be the same as it was when directive finished initialization
expect(countScopes(scope, 0)).toBe(scopeCountAfterShow);
scope.$destroy();
scope = originalScope;
// scope count should be the same as it was before directive was initialized
expect(countScopes(scope, 0)).toBe(scopeCount);
});
});
describe('contentTemplate', function() {
it('should support custom contentTemplate', function() {
$templateCache.put('custom', 'baz: {{title}}');
var elm = compileDirective('options-contentTemplate');
angular.element(elm[0]).triggerHandler('click');
expect(sandboxEl.find('.modal-body').text()).toBe('baz: ' + scope.modal.title);
});
});
describe('container', function() {
it('accepts element object', function() {
var testElm = angular.element('<div></div>');
sandboxEl.append(testElm);
var myModal = $modal(angular.extend({}, templates['default'].scope.modal, {container: testElm}));
scope.$digest();
expect(angular.element(testElm.children()[0]).hasClass('modal')).toBeTruthy();
});
it('accepts data-container element selector', function() {
var testElm = angular.element('<div id="testElm"></div>');
sandboxEl.append(testElm);
var elm = compileDirective('options-container', {container: '#testElm'});
angular.element(elm[0]).triggerHandler('click');
expect(angular.element(testElm.children()[0]).hasClass('modal')).toBeTruthy();
});
it('should belong to sandbox when data-container is falsy', function() {
var elm = compileDirective('options-container', angular.extend({}, templates['default'].scope.modal, {container: 'false'}));
angular.element(elm[0]).triggerHandler('click');
expect(sandboxEl.find('.modal').length).toBe(1);
});
});
describe('backdrop', function() {
it('should show backdrop by default', function() {
var elm = compileDirective('default');
expect(bodyEl.find('.modal-backdrop').length).toBe(0);
angular.element(elm[0]).triggerHandler('click');
expect(bodyEl.find('.modal-backdrop').length).toBe(1);
});
it('should show backdrop if data-backdrop is truthy', function() {
var elm = compileDirective('options-backdrop', {backdrop: 'anything'});
expect(bodyEl.find('.modal-backdrop').length).toBe(0);
angular.element(elm[0]).triggerHandler('click');
expect(bodyEl.find('.modal-backdrop').length).toBe(1);
});
it('should not show backdrop if data-backdrop is false', function() {
var elm = compileDirective('options-backdrop', {backdrop: 'false'});
expect(bodyEl.find('.modal-backdrop').length).toBe(0);
angular.element(elm[0]).triggerHandler('click');
expect(bodyEl.find('.modal-backdrop').length).toBe(0);
});
it('should show backdrop above a previous modal dialog using the z-index value', function() {
var elm1 = compileDirective('default');
var elm2 = compileDirective('default');
expect(bodyEl.find('.modal-backdrop').length).toBe(0);
angular.element(elm1[0]).triggerHandler('click');
expect(bodyEl.find('.modal-backdrop').length).toBe(1);
var backdrop1 = bodyEl.find('.modal-backdrop')[0];
var modal1 = bodyEl.find('.modal')[0];
angular.element(elm2[0]).triggerHandler('click');
expect(bodyEl.find('.modal-backdrop').length).toBe(2);
var backdrop2 = bodyEl.find('.modal-backdrop')[angular.version.minor <= 2 ? 1 : 0];
var modal2 = bodyEl.find('.modal')[1];
expect(angular.element(backdrop1).css('z-index')).toBe('1040');
expect(angular.element(modal1).css('z-index')).toBe('1050');
expect(angular.element(backdrop2).css('z-index')).toBe('1060');
expect(angular.element(modal2).css('z-index')).toBe('1070');
});
});
describe('modalClass', function() {
it('should add class to the modal element', function() {
var elm = compileDirective('options-modalClass');
angular.element(elm[0]).triggerHandler('click');
expect(sandboxEl.children('.modal')).toHaveClass('my-custom-class');
});
it('should not add class to the modal element when modalClass is not present', function() {
var elm = compileDirective('default');
angular.element(elm[0]).triggerHandler('click');
expect(sandboxEl.children('.modal')).not.toHaveClass('my-custom-class');
});
});
describe('size', function() {
it('sets size class when specified', function() {
var elm = compileDirective('options-size-lg');
angular.element(elm[0]).triggerHandler('click');
expect(sandboxEl.find('.modal-dialog')).toHaveClass('modal-lg');
});
it('does not set size class when not specified', function() {
var elm = compileDirective('default');
angular.element(elm[0]).triggerHandler('click');
expect(sandboxEl.find('.modal-dialog')).not.toHaveClass('modal-lg');
});
it('does not set size class when invalid size is specified', function() {
var elm = compileDirective('options-size-invalid');
angular.element(elm[0]).triggerHandler('click');
expect(sandboxEl.find('.modal-dialog')).not.toHaveClass('modal-lg');
});
});
describe('zIndex', function() {
it('does not interfere with the default values', function() {
var elm = compileDirective('default');
angular.element(elm[0]).triggerHandler('click');
var modal = bodyEl.find('.modal')[0];
var backdrop = bodyEl.find('.modal-backdrop')[0];
expect(angular.element(modal).css('z-index')).toBe('1050');
expect(angular.element(backdrop).css('z-index')).toBe('1040');
});
it('sets a custom z-index on a modal and decrements the backdrop z-index by 10', function() {
var elm = compileDirective('options-z-index', {zIndex: 2000});
angular.element(elm[0]).triggerHandler('click');
var modal = bodyEl.find('.modal')[0];
var backdrop = bodyEl.find('.modal-backdrop')[0];
expect(angular.element(modal).css('z-index')).toBe('2000');
expect(angular.element(backdrop).css('z-index')).toBe('1990');
});
});
describe('stackableModals', function() {
it('remove class modal-open from body only when the last modal is closed', function() {
var elm1 = compileDirective('default');
var elm2 = compileDirective('default');
// open modal 1
angular.element(elm1[0]).triggerHandler('click');
expect(sandboxEl.children('.modal').length).toBe(1);
// open modal 2
angular.element(elm2[0]).triggerHandler('click');
expect(sandboxEl.children('.modal').length).toBe(2);
// close modal 2
angular.element(elm2[0]).triggerHandler('click');
$animate.flush(); // hide only fires AFTER the animation is complete
expect(bodyEl.hasClass('modal-open')).toBeTruthy();
// close modal 1
angular.element(elm1[0]).triggerHandler('click');
$animate.flush(); // hide only fires AFTER the animation is complete
expect(bodyEl.hasClass('modal-open')).toBeFalsy();
});
});
describe('onBeforeShow', function() {
it('should invoke beforeShow event callback', function() {
var beforeShow = false;
function onBeforeShow(select) {
beforeShow = true;
}
var elm = compileDirective('options-events', {onBeforeShow: onBeforeShow});
angular.element(elm[0]).triggerHandler('click');
expect(beforeShow).toBe(true);
});
});
describe('onShow', function() {
it('should invoke show event callback', function() {
var show = false;
function onShow(select) {
show = true;
}
var elm = compileDirective('options-events', {onShow: onShow});
angular.element(elm[0]).triggerHandler('click');
$animate.flush();
expect(show).toBe(true);
});
});
describe('onBeforeHide', function() {
it('should invoke beforeHide event callback', function() {
var beforeHide = false;
function onBeforeHide(select) {
beforeHide = true;
}
var elm = compileDirective('options-events', {onBeforeHide: onBeforeHide});
angular.element(elm[0]).triggerHandler('click');
angular.element(elm[0]).triggerHandler('click');
expect(beforeHide).toBe(true);
});
});
describe('onHide', function() {
it('should invoke show event callback', function() {
var hide = false;
function onHide(select) {
hide = true;
}
var elm = compileDirective('options-events', {onHide: onHide});
angular.element(elm[0]).triggerHandler('click');
angular.element(elm[0]).triggerHandler('click');
$animate.flush();
expect(hide).toBe(true);
});
});
});
describe('508', function () {
it('should set aria-hidden false on modal when shown', function () {
var elm = compileDirective('508', {});
expect(bodyEl.find('.modal').length).toBe(0);
angular.element(elm[0]).triggerHandler('click');
var modal = bodyEl.find('.modal');
expect(modal.length).toBe(1);
$animate.flush();
expect(jQuery(modal).attr('aria-hidden')).toBe('false');
});
it('should set aria-hidden true on body when shown', function () {
var elm = compileDirective('508', {});
expect(bodyEl.find('.modal').length).toBe(0);
angular.element(elm[0]).triggerHandler('click');
var modal = bodyEl.find('.modal');
expect(modal.length).toBe(1);
$animate.flush();
expect(jQuery(bodyEl).attr('aria-hidden')).toBe('true');
});
it('should focus the modal on open', function () {
var elm = compileDirective('508', {});
expect(bodyEl.find('.modal').length).toBe(0);
angular.element(elm[0]).triggerHandler('click');
var modal = bodyEl.find('.modal');
expect(modal.length).toBe(1);
spyOn(modal[0], 'focus')
$animate.flush();
expect(modal[0].focus).toHaveBeenCalled();
});
it('should focus the button when closed', function () {
var elm = compileDirective('508', {});
expect(bodyEl.find('.modal').length).toBe(0);
angular.element(elm[0]).triggerHandler('click');
var modal = bodyEl.find('.modal');
expect(modal.length).toBe(1);
$animate.flush();
spyOn(elm[0], 'focus');
jQuery('.modal BUTTON.btn.btn-default').triggerHandler('click');
modal = bodyEl.find('.modal');
expect(modal.length).toBe(0);
expect(elm[0].focus).toHaveBeenCalled();
});
it('should keep focus in the modal when the tab is triggered from the focused close button', function () {
var elm = compileDirective('508', {});
expect(bodyEl.find('.modal').length).toBe(0);
angular.element(elm[0]).triggerHandler('click');
var modal = bodyEl.find('.modal');
expect(modal.length).toBe(1);
$animate.flush();
jQuery('.modal BUTTON.btn.btn-default')[0].focus();
var evt = jQuery.Event( 'keydown', { keyCode: 9, which: 9 } );
var close = jQuery('.modal BUTTON.close');
spyOn(close[0], 'focus');
modal.triggerHandler(evt);
scope.$digest();
expect(close[0].focus).toHaveBeenCalled();
});
it('should keep focus in the modal when shift+tab is triggered from the focused close button', function () {
var elm = compileDirective('508', {});
expect(bodyEl.find('.modal').length).toBe(0);
angular.element(elm[0]).triggerHandler('click');
var modal = bodyEl.find('.modal');
expect(modal.length).toBe(1);
$animate.flush();
jQuery('.modal BUTTON.close')[0].focus();
var evt = jQuery.Event( 'keydown', { keyCode: 9, which: 9, shiftKey: true } );
var close = jQuery('.modal BUTTON.btn.btn-default');
spyOn(close[0], 'focus');
modal.triggerHandler(evt);
scope.$digest();
expect(close[0].focus).toHaveBeenCalled();
});
});
});