angular-ui-bootstrap
Version:
Native AngularJS (Angular) directives for Bootstrap
1,455 lines (1,167 loc) • 71.8 kB
JavaScript
describe('$uibResolve', function() {
beforeEach(module('ui.bootstrap.modal'));
it('should resolve invocables and return promise with object of resolutions', function() {
module(function($provide) {
$provide.factory('bar', function() {
return 'bar';
});
});
inject(function($q, $rootScope, $uibResolve) {
$uibResolve.resolve({
foo: 'bar',
bar: $q.resolve('baz'),
baz: function() {
return 'boo';
}
}).then(function(resolves) {
expect(resolves).toEqual({
foo: 'bar',
bar: 'baz',
baz: 'boo'
});
});
$rootScope.$digest();
});
});
describe('with custom resolver', function() {
beforeEach(module(function($provide, $uibResolveProvider) {
$provide.factory('$resolve', function() {
return {
resolve: jasmine.createSpy()
};
});
$uibResolveProvider.setResolver('$resolve');
}));
it('should call $resolve.resolve', inject(function($uibResolve, $resolve) {
$uibResolve.resolve({foo: 'bar'}, {}, null, null);
expect($resolve.resolve).toHaveBeenCalledWith({foo: 'bar'}, {}, null, null);
}));
});
});
describe('uibModalTransclude', function() {
var uibModalTranscludeDDO,
$animate;
beforeEach(module('ui.bootstrap.modal'));
beforeEach(module(function($provide) {
$animate = jasmine.createSpyObj('$animate', ['enter']);
$provide.value('$animate', $animate);
}));
beforeEach(inject(function(uibModalTranscludeDirective) {
uibModalTranscludeDDO = uibModalTranscludeDirective[0];
}));
describe('when initialised', function() {
var scope,
element,
transcludeSpy,
transcludeFn;
beforeEach(function() {
scope = {
$parent: 'parentScope'
};
element = jasmine.createSpyObj('containerElement', ['empty']);
transcludeSpy = jasmine.createSpy('transcludeSpy').and.callFake(function(scope, fn) {
transcludeFn = fn;
});
uibModalTranscludeDDO.link(scope, element, {}, {}, transcludeSpy);
});
it('should call the transclusion function', function() {
expect(transcludeSpy).toHaveBeenCalledWith(scope.$parent, jasmine.any(Function));
});
describe('transclusion callback', function() {
var transcludedContent;
beforeEach(function() {
transcludedContent = 'my transcluded content';
transcludeFn(transcludedContent);
});
it('should empty the element', function() {
expect(element.empty).toHaveBeenCalledWith();
});
it('should append the transcluded content', function() {
expect($animate.enter).toHaveBeenCalledWith(transcludedContent, element);
});
});
});
});
describe('$uibModal', function() {
var $animate, $controllerProvider, $rootScope, $document, $compile, $templateCache, $timeout, $q;
var $uibModal, $uibModalStack, $uibModalProvider;
beforeEach(module('ngAnimateMock'));
beforeEach(module('ui.bootstrap.modal'));
beforeEach(module('uib/template/modal/window.html'));
beforeEach(module(function(_$controllerProvider_, _$uibModalProvider_, $compileProvider) {
$controllerProvider = _$controllerProvider_;
$uibModalProvider = _$uibModalProvider_;
$compileProvider.directive('parentDirective', function() {
return {
controller: function() {
this.text = 'foo';
}
};
}).directive('childDirective', function() {
return {
require: '^parentDirective',
link: function(scope, elem, attrs, ctrl) {
scope.text = ctrl.text;
}
};
}).directive('focusMe', function() {
return {
link: function(scope, elem, attrs) {
elem.focus();
}
};
}).component('fooBar', {
bindings: {
resolve: '<',
modalInstance: '<',
close: '&',
dismiss: '&'
},
controller: angular.noop,
controllerAs: 'foobar',
template: '<div>Foo Bar</div>'
});
}));
beforeEach(inject(function(_$animate_, _$rootScope_, _$document_, _$compile_, _$templateCache_, _$timeout_, _$q_, _$uibModal_, _$uibModalStack_) {
$animate = _$animate_;
$rootScope = _$rootScope_;
$document = _$document_;
$compile = _$compile_;
$templateCache = _$templateCache_;
$timeout = _$timeout_;
$q = _$q_;
$uibModal = _$uibModal_;
$uibModalStack = _$uibModalStack_;
}));
beforeEach(function() {
jasmine.addMatchers({
toBeResolvedWith: function(util, customEqualityTesters) {
return {
compare: function(promise, expected) {
var called = false;
promise.then(function(result) {
expect(result).toEqual(expected);
if (result === expected) {
result.message = 'Expected "' + angular.mock.dump(result) + '" not to be resolved with "' + expected + '".';
} else {
result.message = 'Expected "' + angular.mock.dump(result) + '" to be resolved with "' + expected + '".';
}
}, function(result) {
fail('Expected "' + angular.mock.dump(result) + '" to be resolved with "' + expected + '".');
})['finally'](function() {
called = true;
});
$rootScope.$digest();
if (!called) {
fail('Expected "' + angular.mock.dump(result) + '" to be resolved with "' + expected + '".');
}
return {pass: true};
}
};
},
toBeRejectedWith: function(util, customEqualityTesters) {
return {
compare: function(promise, expected) {
var result = {};
var called = false;
promise.then(function(result) {
fail('Expected "' + angular.mock.dump(result) + '" to be rejected with "' + expected + '".');
}, function(result) {
expect(result).toEqual(expected);
if (result === expected) {
result.message = 'Expected "' + angular.mock.dump(result) + '" not to be rejected with "' + expected + '".';
} else {
result.message = 'Expected "' + angular.mock.dump(result) + '" to be rejected with "' + expected + '".';
}
})['finally'](function() {
called = true;
});
$rootScope.$digest();
if (!called) {
fail('Expected "' + angular.mock.dump(result) + '" to be rejected with "' + expected + '".');
}
return {pass: true};
}
};
},
toHaveModalOpenWithContent: function(util, customEqualityTesters) {
return {
compare: function(actual, content, selector) {
var contentToCompare, modalDomEls = actual.find('body > div.modal > div.modal-dialog > div.modal-content');
contentToCompare = selector ? modalDomEls.find(selector) : modalDomEls;
var result = {
pass: modalDomEls.css('display') === 'block' && contentToCompare.html() === content
};
if (result.pass) {
result.message = '"Expected "' + angular.mock.dump(modalDomEls) + '" not to be open with "' + content + '".';
} else {
result.message = '"Expected "' + angular.mock.dump(modalDomEls) + '" to be open with "' + content + '".';
}
return result;
}
};
},
toHaveModalsOpen: function(util, customEqualityTesters) {
return {
compare: function(actual, expected) {
var modalDomEls = actual.find('body > div.modal');
var result = {
pass: util.equals(modalDomEls.length, expected, customEqualityTesters)
};
if (result.pass) {
result.message = 'Expected "' + angular.mock.dump(modalDomEls) + '" not to have "' + expected + '" modals opened.';
} else {
result.message = 'Expected "' + angular.mock.dump(modalDomEls) + '" to have "' + expected + '" modals opened.';
}
return result;
}
};
},
toHaveBackdrop: function(util, customEqualityTesters) {
return {
compare: function(actual, expected) {
var backdropDomEls = actual.find('body > div.modal-backdrop');
var result = {
pass: util.equals(backdropDomEls.length, 1, customEqualityTesters)
};
if (result.pass) {
result.message = 'Expected "' + angular.mock.dump(backdropDomEls) + '" not to be a backdrop element".';
} else {
result.message = 'Expected "' + angular.mock.dump(backdropDomEls) + '" to be a backdrop element".';
}
return result;
}
};
}
});
});
afterEach(function () {
var body = $document.find('body');
body.find('div.modal').remove();
body.find('div.modal-backdrop').remove();
body.removeClass('modal-open');
$document.off('keydown');
});
function triggerKeyDown(element, keyCode, shiftKey) {
var e = $.Event('keydown');
e.srcElement = element[0];
e.which = keyCode;
e.shiftKey = shiftKey;
element.trigger(e);
}
function open(modalOptions, noFlush, noDigest) {
var modal = $uibModal.open(modalOptions);
modal.opened['catch'](angular.noop);
modal.result['catch'](angular.noop);
if (!noDigest) {
$rootScope.$digest();
if (!noFlush) {
$animate.flush();
}
}
return modal;
}
function close(modal, result, noFlush) {
var closed = modal.close(result);
$rootScope.$digest();
if (!noFlush) {
$animate.flush();
$rootScope.$digest();
$animate.flush();
$rootScope.$digest();
}
return closed;
}
function dismiss(modal, reason, noFlush) {
var closed = modal.dismiss(reason);
$rootScope.$digest();
if (!noFlush) {
$animate.flush();
$rootScope.$digest();
$animate.flush();
$rootScope.$digest();
}
return closed;
}
describe('basic scenarios with default options', function() {
it('should open and dismiss a modal with a minimal set of options', function() {
var modal = open({template: '<div>Content</div>'});
expect($document).toHaveModalsOpen(1);
expect($document).toHaveModalOpenWithContent('Content', 'div');
expect($document).toHaveBackdrop();
dismiss(modal, 'closing in test');
expect($document).toHaveModalsOpen(0);
expect($document).not.toHaveBackdrop();
});
it('should compile modal before inserting into DOM', function() {
var topModal;
var modalInstance = {
result: $q.defer(),
opened: $q.defer(),
closed: $q.defer(),
rendered: $q.defer(),
close: function (result) {
return $uibModalStack.close(modalInstance, result);
},
dismiss: function (reason) {
return $uibModalStack.dismiss(modalInstance, reason);
}
};
var expectedText = 'test';
$uibModalStack.open(modalInstance, {
appendTo: angular.element(document.body),
scope: $rootScope.$new(),
deferred: modalInstance.result,
renderDeferred: modalInstance.rendered,
closedDeferred: modalInstance.closed,
content: '<div id="test">{{\'' + expectedText + '\'}}</div>'
});
topModal = $uibModalStack.getTop();
expect(topModal.value.modalDomEl.find('#test').length).toEqual(0);
expect(angular.element('#test').length).toEqual(0);
$rootScope.$digest();
expect(topModal.value.modalDomEl.find('#test').text()).toEqual(expectedText);
expect(angular.element('#test').text()).toEqual(expectedText);
$animate.flush();
close(modalInstance, 'closing in test', true);
});
it('should resolve rendered promise when animation is complete', function() {
var modalInstance = {
result: $q.defer(),
opened: $q.defer(),
closed: $q.defer(),
rendered: $q.defer(),
close: function (result) {
return $uibModalStack.close(modalInstance, result);
},
dismiss: function (reason) {
return $uibModalStack.dismiss(modalInstance, reason);
}
};
var rendered = false;
modalInstance.rendered.promise.then(function() {
rendered = true;
});
$uibModalStack.open(modalInstance, {
appendTo: angular.element(document.body),
scope: $rootScope.$new(),
deferred: modalInstance.result,
renderDeferred: modalInstance.rendered,
closedDeferred: modalInstance.closed,
content: '<div id="test">test</div>'
});
$rootScope.$digest();
expect(rendered).toBe(false);
$animate.flush();
expect(rendered).toBe(true);
});
it('should not throw an exception on a second dismiss', function() {
var modal = open({template: '<div>Content</div>'});
expect($document).toHaveModalsOpen(1);
expect($document).toHaveModalOpenWithContent('Content', 'div');
expect($document).toHaveBackdrop();
dismiss(modal, 'closing in test');
expect($document).toHaveModalsOpen(0);
dismiss(modal, 'closing in test', true);
});
it('should not throw an exception on a second close', function() {
var modal = open({template: '<div>Content</div>'});
expect($document).toHaveModalsOpen(1);
expect($document).toHaveModalOpenWithContent('Content', 'div');
expect($document).toHaveBackdrop();
close(modal, 'closing in test');
expect($document).toHaveModalsOpen(0);
close(modal, 'closing in test', true);
});
it('should open a modal from templateUrl', function() {
$templateCache.put('content.html', '<div>URL Content</div>');
var modal = open({templateUrl: 'content.html'});
expect($document).toHaveModalsOpen(1);
expect($document).toHaveModalOpenWithContent('URL Content', 'div');
expect($document).toHaveBackdrop();
dismiss(modal, 'closing in test');
expect($document).toHaveModalsOpen(0);
expect($document).not.toHaveBackdrop();
});
it('should support closing on ESC', function() {
var modal = open({template: '<div>Content</div>'});
expect($document).toHaveModalsOpen(1);
triggerKeyDown($document, 27);
$animate.flush();
$rootScope.$digest();
$animate.flush();
$rootScope.$digest();
expect($document).toHaveModalsOpen(0);
});
it('should not close on ESC if event.preventDefault() was issued', function() {
var modal = open({template: '<div><button>x</button></div>' });
expect($document).toHaveModalsOpen(1);
var button = angular.element('button').on('keydown', preventKeyDown);
triggerKeyDown(button, 27);
$rootScope.$digest();
expect($document).toHaveModalsOpen(1);
button.off('keydown', preventKeyDown);
triggerKeyDown(button, 27);
$animate.flush();
$rootScope.$digest();
$animate.flush();
$rootScope.$digest();
expect($document).toHaveModalsOpen(0);
function preventKeyDown(evt) {
evt.preventDefault();
}
});
it('should support closing on backdrop click', function() {
var modal = open({template: '<div>Content</div>'});
expect($document).toHaveModalsOpen(1);
$document.find('body > div.modal').click();
$animate.flush();
$rootScope.$digest();
$animate.flush();
$rootScope.$digest();
expect($document).toHaveModalsOpen(0);
});
it('should return to the element which had focus before the dialog was invoked', function() {
var link = '<a href>Link</a>';
var element = angular.element(link);
angular.element(document.body).append(element);
element.focus();
expect(document.activeElement.tagName).toBe('A');
var modal = open({template: '<div>Content<button>inside modal</button></div>'});
$rootScope.$digest();
expect(document.activeElement.className.split(' ')).toContain('modal');
expect($document).toHaveModalsOpen(1);
triggerKeyDown($document, 27);
$animate.flush();
$rootScope.$digest();
$animate.flush();
$rootScope.$digest();
expect(document.activeElement.tagName).toBe('A');
expect($document).toHaveModalsOpen(0);
element.remove();
});
it('should return to document.body if element which had focus before the dialog was invoked is gone, or is missing focus function', function() {
var link = '<a href>Link</a>';
var element = angular.element(link);
angular.element(document.body).append(element);
element.focus();
expect(document.activeElement.tagName).toBe('A');
var modal = open({template: '<div>Content</div>'});
$rootScope.$digest();
expect(document.activeElement.tagName).toBe('DIV');
expect($document).toHaveModalsOpen(1);
// Fake undefined focus function, happening in IE in certain
// iframe conditions. See issue 3639
element[0].focus = undefined;
triggerKeyDown($document, 27);
$animate.flush();
$rootScope.$digest();
$animate.flush();
$rootScope.$digest();
expect(document.activeElement.tagName).toBe('BODY');
expect($document).toHaveModalsOpen(0);
element.remove();
});
it('should resolve returned promise on close', function() {
var modal = open({template: '<div>Content</div>'});
close(modal, 'closed ok');
expect(modal.result).toBeResolvedWith('closed ok');
});
it('should reject returned promise on dismiss', function() {
var modal = open({template: '<div>Content</div>'});
dismiss(modal, 'esc');
expect(modal.result).toBeRejectedWith('esc');
});
it('should reject returned promise on unexpected closure', function() {
var scope = $rootScope.$new();
var modal = open({template: '<div>Content</div>', scope: scope});
scope.$destroy();
expect(modal.result).toBeRejectedWith('$uibUnscheduledDestruction');
$animate.flush();
$rootScope.$digest();
$animate.flush();
$rootScope.$digest();
expect($document).toHaveModalsOpen(0);
});
it('should resolve the closed promise when modal is closed', function() {
var modal = open({template: '<div>Content</div>'});
var closed = false;
close(modal, 'closed ok');
modal.closed.then(function() {
closed = true;
});
$rootScope.$digest();
expect(closed).toBe(true);
});
it('should resolve the closed promise when modal is dismissed', function() {
var modal = open({template: '<div>Content</div>'});
var closed = false;
dismiss(modal, 'esc');
modal.closed.then(function() {
closed = true;
});
$rootScope.$digest();
expect(closed).toBe(true);
});
it('should expose a promise linked to the templateUrl / resolve promises', function() {
var modal = open({template: '<div>Content</div>', resolve: {
ok: function() {return $q.when('ok');}
}}
);
expect(modal.opened).toBeResolvedWith(true);
});
it('should expose a promise linked to the templateUrl / resolve promises and reject it if needed', function() {
var modal = open({template: '<div>Content</div>', resolve: {
ok: function() {return $q.reject('ko');}
}}, true);
expect(modal.opened).toBeRejectedWith('ko');
});
it('should focus on the element that has autofocus attribute when the modal is open/reopen and the animations have finished', function() {
function openAndCloseModalWithAutofocusElement() {
var modal = open({template: '<div><input type="text" id="auto-focus-element" autofocus></div>'});
$rootScope.$digest();
expect(angular.element('#auto-focus-element')).toHaveFocus();
close(modal, 'closed ok');
expect(modal.result).toBeResolvedWith('closed ok');
}
openAndCloseModalWithAutofocusElement();
openAndCloseModalWithAutofocusElement();
});
it('should not focus on the element that has autofocus attribute when the modal is opened and something in the modal already has focus and the animations have finished', function() {
function openAndCloseModalWithAutofocusElement() {
var modal = open({template: '<div><input type="text" id="pre-focus-element" focus-me><input type="text" id="auto-focus-element" autofocus></div>'});
$rootScope.$digest();
expect(angular.element('#auto-focus-element')).not.toHaveFocus();
expect(angular.element('#pre-focus-element')).toHaveFocus();
close(modal, 'closed ok');
expect(modal.result).toBeResolvedWith('closed ok');
}
openAndCloseModalWithAutofocusElement();
openAndCloseModalWithAutofocusElement();
});
it('should wait until the in animation is finished before attempting to focus the modal or autofocus element', function() {
function openAndCloseModalWithAutofocusElement() {
var modal = open({template: '<div><input type="text" id="auto-focus-element" autofocus></div>'}, true, true);
expect(angular.element('#auto-focus-element')).not.toHaveFocus();
$rootScope.$digest();
$animate.flush();
expect(angular.element('#auto-focus-element')).toHaveFocus();
close(modal, 'closed ok');
expect(modal.result).toBeResolvedWith('closed ok');
}
function openAndCloseModalWithOutAutofocusElement() {
var link = '<a href>Link</a>';
var element = angular.element(link);
angular.element(document.body).append(element);
element.focus();
expect(document.activeElement.tagName).toBe('A');
var modal = open({template: '<div><input type="text"></div>'}, true, true);
expect(document.activeElement.tagName).toBe('A');
$rootScope.$digest();
$animate.flush();
expect(document.activeElement.className.split(' ')).toContain('modal');
close(modal, 'closed ok');
expect(modal.result).toBeResolvedWith('closed ok');
element.remove();
}
openAndCloseModalWithAutofocusElement();
openAndCloseModalWithOutAutofocusElement();
});
it('should change focus to first element when tab key was pressed', function() {
var initialPage = angular.element('<a href="#" id="cannot-get-focus-from-modal">Outland link</a>');
angular.element(document.body).append(initialPage);
initialPage.focus();
open({
template:'<a href="#" id="tab-focus-link"><input type="text" id="tab-focus-input1"/><input type="text" id="tab-focus-input2"/>' +
'<button id="tab-focus-button">Open me!</button>'
});
expect($document).toHaveModalsOpen(1);
var lastElement = angular.element(document.getElementById('tab-focus-button'));
lastElement.focus();
triggerKeyDown(lastElement, 9);
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link');
initialPage.remove();
});
it('should change focus to last element when shift+tab key is pressed', function() {
var initialPage = angular.element('<a href="#" id="cannot-get-focus-from-modal">Outland link</a>');
angular.element(document.body).append(initialPage);
initialPage.focus();
open({
template:'<a href="#" id="tab-focus-link"><input type="text" id="tab-focus-input1"/><input type="text" id="tab-focus-input2"/>' +
'<button id="tab-focus-button">Open me!</button>'
});
$rootScope.$digest();
expect($document).toHaveModalsOpen(1);
triggerKeyDown(angular.element(document.activeElement), 9, true);
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-button');
var lastElement = angular.element(document.getElementById('tab-focus-link'));
lastElement.focus();
triggerKeyDown(angular.element(document.activeElement), 9, true);
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-button');
initialPage.remove();
});
it('should change focus to first element when tab key is pressed when keyboard is false', function() {
var initialPage = angular.element('<a href="#" id="cannot-get-focus-from-modal">Outland link</a>');
angular.element(document.body).append(initialPage);
initialPage.focus();
open({
template:'<a href="#" id="tab-focus-link"><input type="text" id="tab-focus-input1"/><input type="text" id="tab-focus-input2"/>' +
'<button id="tab-focus-button">Open me!</button>',
keyboard: false
});
expect($document).toHaveModalsOpen(1);
var lastElement = angular.element(document.getElementById('tab-focus-button'));
lastElement.focus();
triggerKeyDown(lastElement, 9);
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link');
initialPage.remove();
});
it('should change focus to last element when shift+tab keys are pressed when keyboard is false', function() {
var initialPage = angular.element('<a href="#" id="cannot-get-focus-from-modal">Outland link</a>');
angular.element(document.body).append(initialPage);
initialPage.focus();
open({
template:'<a href="#" id="tab-focus-link"><input type="text" id="tab-focus-input1"/><input type="text" id="tab-focus-input2"/>' +
'<button id="tab-focus-button">Open me!</button>',
keyboard: false
});
$rootScope.$digest();
expect($document).toHaveModalsOpen(1);
triggerKeyDown(angular.element(document.activeElement), 9, true);
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-button');
var lastElement = angular.element(document.getElementById('tab-focus-link'));
lastElement.focus();
triggerKeyDown(angular.element(document.activeElement), 9, true);
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-button');
initialPage.remove();
});
it('should change focus to next proper element when DOM changes and tab is pressed', function() {
var initialPage = angular.element('<a href="#" id="cannot-get-focus-from-modal">Outland link</a>');
angular.element(document.body).append(initialPage);
initialPage.focus();
open({
template:'<a href="#" id="tab-focus-link1">a</a><a href="#" id="tab-focus-link2">b</a><a href="#" id="tab-focus-link3">c</a>' +
'<button id="tab-focus-button">Open me!</button>',
keyboard: false
});
$rootScope.$digest();
expect($document).toHaveModalsOpen(1);
$('#tab-focus-link3').focus();
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link3');
$('#tab-focus-button').remove();
triggerKeyDown(angular.element(document.activeElement), 9, false);
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link1');
initialPage.remove();
});
it('should change focus to next proper element when DOM changes and shift+tab is pressed', function() {
var initialPage = angular.element('<a href="#" id="cannot-get-focus-from-modal">Outland link</a>');
angular.element(document.body).append(initialPage);
initialPage.focus();
open({
template:'<a href="#" id="tab-focus-link1">a</a><a href="#" id="tab-focus-link2">b</a><a href="#" id="tab-focus-link3">c</a>' +
'<button id="tab-focus-button">Open me!</button>',
keyboard: false
});
$rootScope.$digest();
expect($document).toHaveModalsOpen(1);
$('#tab-focus-link1').focus();
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link1');
$('#tab-focus-button').remove();
triggerKeyDown(angular.element(document.activeElement), 9, true);
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link3');
initialPage.remove();
});
it('should change focus to next non-hidden element when tab is pressed', function() {
var initialPage = angular.element('<a href="#" id="cannot-get-focus-from-modal">Outland link</a>');
angular.element(document.body).append(initialPage);
initialPage.focus();
open({
template:'<a href="#" id="tab-focus-link1">a</a><a href="#" id="tab-focus-link2">b</a><a href="#" id="tab-focus-link3">c</a>' +
'<button id="tab-focus-button">Open me!</button>',
keyboard: false
});
$rootScope.$digest();
expect($document).toHaveModalsOpen(1);
$('#tab-focus-link3').focus();
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link3');
$('#tab-focus-button').css('display', 'none');
triggerKeyDown(angular.element(document.activeElement), 9, false);
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link1');
initialPage.remove();
});
it('should change focus to previous non-hidden element when shift+tab is pressed', function() {
var initialPage = angular.element('<a href="#" id="cannot-get-focus-from-modal">Outland link</a>');
angular.element(document.body).append(initialPage);
initialPage.focus();
open({
template:'<a href="#" id="tab-focus-link1">a</a><a href="#" id="tab-focus-link2">b</a><a href="#" id="tab-focus-link3">c</a>' +
'<button id="tab-focus-button">Open me!</button>',
keyboard: false
});
$rootScope.$digest();
expect($document).toHaveModalsOpen(1);
$('#tab-focus-link1').focus();
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link1');
$('#tab-focus-button').css('display', 'none');
triggerKeyDown(angular.element(document.activeElement), 9, true);
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link3');
initialPage.remove();
});
it('should change focus to next tabbable element when tab is pressed', function() {
var initialPage = angular.element('<a href="#" id="cannot-get-focus-from-modal">Outland link</a>');
angular.element(document.body).append(initialPage);
initialPage.focus();
open({
template:'<button id="tab-focus-button1" tabindex="-1">Skip me!</button><a href="#" id="tab-focus-link1">a</a>' +
'<a href="#" id="tab-focus-link2">b</a><a href="#" id="tab-focus-link3">c</a>' +
'<button id="tab-focus-button2" tabindex="-1">Skip me!</button>',
keyboard: false
});
$rootScope.$digest();
expect($document).toHaveModalsOpen(1);
$('#tab-focus-link3').focus();
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link3');
triggerKeyDown(angular.element(document.activeElement), 9, false);
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link1');
initialPage.remove();
});
it('should change focus to previous tabbable element when shift+tab is pressed', function() {
var initialPage = angular.element('<a href="#" id="cannot-get-focus-from-modal">Outland link</a>');
angular.element(document.body).append(initialPage);
initialPage.focus();
open({
template:'<button id="tab-focus-button1" tabindex="-1">Skip me!</button><a href="#" id="tab-focus-link1">a</a>' +
'<a href="#" id="tab-focus-link2">b</a><a href="#" id="tab-focus-link3">c</a>' +
'<button id="tab-focus-button2" tabindex="-1">Skip me!</button>',
keyboard: false
});
$rootScope.$digest();
expect($document).toHaveModalsOpen(1);
$('#tab-focus-link1').focus();
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link1');
triggerKeyDown(angular.element(document.activeElement), 9, true);
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link3');
initialPage.remove();
});
});
describe('default options can be changed in a provider', function() {
it('should allow overriding default options in a provider', function() {
$uibModalProvider.options.backdrop = false;
var modal = open({template: '<div>Content</div>'});
expect($document).toHaveModalOpenWithContent('Content', 'div');
expect($document).not.toHaveBackdrop();
});
it('should accept new objects with default options in a provider', function() {
$uibModalProvider.options = {
backdrop: false
};
var modal = open({template: '<div>Content</div>'});
expect($document).toHaveModalOpenWithContent('Content', 'div');
expect($document).not.toHaveBackdrop();
});
});
describe('option by option', function() {
describe('component', function() {
function getModalComponent($document) {
return $document.find('body > div.modal > div.modal-dialog > div.modal-content foo-bar');
}
it('should use as modal content', function() {
open({
component: 'fooBar'
});
var component = getModalComponent($document);
expect(component.html()).toBe('<div>Foo Bar</div>');
});
it('should bind expected values', function() {
var modal = open({
component: 'fooBar',
resolve: {
foo: function() {
return 'bar';
}
}
});
var component = getModalComponent($document);
var componentScope = component.isolateScope();
expect(componentScope.foobar.resolve.foo).toBe('bar');
expect(componentScope.foobar.modalInstance).toBe(modal);
expect(componentScope.foobar.close).toEqual(jasmine.any(Function));
expect(componentScope.foobar.dismiss).toEqual(jasmine.any(Function));
});
it('should close the modal', function() {
var modal = open({
component: 'fooBar',
resolve: {
foo: function() {
return 'bar';
}
}
});
var component = getModalComponent($document);
var componentScope = component.isolateScope();
componentScope.foobar.close({
$value: 'baz'
});
expect(modal.result).toBeResolvedWith('baz');
});
it('should dismiss the modal', function() {
var modal = open({
component: 'fooBar',
resolve: {
foo: function() {
return 'bar';
}
}
});
var component = getModalComponent($document);
var componentScope = component.isolateScope();
componentScope.foobar.dismiss({
$value: 'baz'
});
expect(modal.result).toBeRejectedWith('baz');
});
});
describe('template and templateUrl', function() {
it('should throw an error if none of component, template and templateUrl are provided', function() {
expect(function(){
var modal = open({});
}).toThrow(new Error('One of component or template or templateUrl options is required.'));
});
it('should not fail if a templateUrl contains leading / trailing white spaces', function() {
$templateCache.put('whitespace.html', ' <div>Whitespaces</div> ');
open({templateUrl: 'whitespace.html'});
expect($document).toHaveModalOpenWithContent('Whitespaces', 'div');
});
it('should accept template as a function', function() {
open({template: function() {
return '<div>From a function</div>';
}});
expect($document).toHaveModalOpenWithContent('From a function', 'div');
});
it('should not fail if a templateUrl as a function', function() {
$templateCache.put('whitespace.html', ' <div>Whitespaces</div> ');
open({templateUrl: function() {
return 'whitespace.html';
}});
expect($document).toHaveModalOpenWithContent('Whitespaces', 'div');
});
});
describe('controller', function() {
it('should accept controllers and inject modal instances', function() {
var TestCtrl = function($scope, $uibModalInstance) {
$scope.fromCtrl = 'Content from ctrl';
$scope.isModalInstance = angular.isObject($uibModalInstance) && angular.isFunction($uibModalInstance.close);
};
open({template: '<div>{{fromCtrl}} {{isModalInstance}}</div>', controller: TestCtrl});
expect($document).toHaveModalOpenWithContent('Content from ctrl true', 'div');
});
it('should accept controllerAs alias', function() {
$controllerProvider.register('TestCtrl', function($uibModalInstance) {
this.fromCtrl = 'Content from ctrl';
this.isModalInstance = angular.isObject($uibModalInstance) && angular.isFunction($uibModalInstance.close);
});
open({template: '<div>{{test.fromCtrl}} {{test.isModalInstance}}</div>', controller: 'TestCtrl as test'});
expect($document).toHaveModalOpenWithContent('Content from ctrl true', 'div');
});
it('should respect the controllerAs property as an alternative for the controller-as syntax', function() {
$controllerProvider.register('TestCtrl', function($uibModalInstance) {
this.fromCtrl = 'Content from ctrl';
this.isModalInstance = angular.isObject($uibModalInstance) && angular.isFunction($uibModalInstance.close);
});
open({template: '<div>{{test.fromCtrl}} {{test.isModalInstance}}</div>', controller: 'TestCtrl', controllerAs: 'test'});
expect($document).toHaveModalOpenWithContent('Content from ctrl true', 'div');
});
it('should allow defining in-place controller-as controllers', function() {
open({template: '<div>{{test.fromCtrl}} {{test.isModalInstance}}</div>', controller: function($uibModalInstance) {
this.fromCtrl = 'Content from ctrl';
this.isModalInstance = angular.isObject($uibModalInstance) && angular.isFunction($uibModalInstance.close);
}, controllerAs: 'test'});
expect($document).toHaveModalOpenWithContent('Content from ctrl true', 'div');
});
it('should allow usage of bindToController', function() {
var $scope = $rootScope.$new(true);
$scope.foo = 'bar';
open({
template: '<div>{{test.fromCtrl}} {{test.closeDismissPresent()}} {{test.foo}}</div>',
controller: function($uibModalInstance) {
expect(this.foo).toEqual($scope.foo);
this.fromCtrl = 'Content from ctrl';
this.closeDismissPresent = function() {
return angular.isFunction(this.$close) && angular.isFunction(this.$dismiss);
};
},
controllerAs: 'test',
bindToController: true,
scope: $scope
});
expect($document).toHaveModalOpenWithContent('Content from ctrl true bar', 'div');
});
it('should have $onInit called', function() {
var $scope = $rootScope.$new(true);
var $onInit = jasmine.createSpy('$onInit');
$scope.foo = 'bar';
open({
template: '<div>{{test.fromCtrl}} {{test.closeDismissPresent()}} {{test.foo}}</div>',
controller: function($uibModalInstance) {
this.$onInit = $onInit;
this.fromCtrl = 'Content from ctrl';
this.closeDismissPresent = function() {
return angular.isFunction(this.$close) && angular.isFunction(this.$dismiss);
};
},
controllerAs: 'test',
bindToController: true,
scope: $scope
});
expect($document).toHaveModalOpenWithContent('Content from ctrl true bar', 'div');
expect($onInit).toHaveBeenCalled();
});
});
describe('resolve', function() {
var ExposeCtrl = function($scope, value) {
$scope.value = value;
};
function modalDefinition(template, resolve) {
return {
template: template,
controller: ExposeCtrl,
resolve: resolve
};
}
it('should resolve simple values', function() {
open(modalDefinition('<div>{{value}}</div>', {
value: function() {
return 'Content from resolve';
}
}));
expect($document).toHaveModalOpenWithContent('Content from resolve', 'div');
});
it('should resolve string references to injectables', function() {
open({
controller: function($scope, $foo) {
$scope.value = 'Content from resolve';
expect($foo).toBe($uibModal);
},
resolve: {
$foo: '$uibModal'
},
template: '<div>{{value}}</div>'
});
expect($document).toHaveModalOpenWithContent('Content from resolve', 'div');
});
it('should resolve promises as promises', function() {
open({
controller: function($scope, $foo) {
$scope.value = 'Content from resolve';
expect($foo).toBe('bar');
},
resolve: {
$foo: $q.when('bar')
},
template: '<div>{{value}}</div>'
});
});
it('should delay showing modal if one of the resolves is a promise', function() {
open(modalDefinition('<div>{{value}}</div>', {
value: function() {
return $timeout(function() { return 'Promise'; }, 100);
}
}), true);
expect($document).toHaveModalsOpen(0);
$timeout.flush();
expect($document).toHaveModalOpenWithContent('Promise', 'div');
});
it('should not open dialog (and reject returned promise) if one of resolve fails', function() {
var deferred = $q.defer();
var modal = open(modalDefinition('<div>{{value}}</div>', {
value: function() {
return deferred.promise;
}
}), true);
expect($document).toHaveModalsOpen(0);
deferred.reject('error in test');
$rootScope.$digest();
expect($document).toHaveModalsOpen(0);
expect(modal.result).toBeRejectedWith('error in test');
});
it('should support injection with minification-safe syntax in resolve functions', function() {
open(modalDefinition('<div>{{value.id}}</div>', {
value: ['$locale', function(e) {
return e;
}]
}));
expect($document).toHaveModalOpenWithContent('en-us', 'div');
});
});
describe('scope', function() {
it('should use custom scope if provided', function() {
var $scope = $rootScope.$new();
$scope.fromScope = 'Content from custom scope';
open({
template: '<div>{{fromScope}}</div>',
scope: $scope
});
expect($document).toHaveModalOpenWithContent('Content from custom scope', 'div');
});
it('should create and use child of $rootScope if custom scope not provided', function() {
var scopeTailBefore = $rootScope.$$childTail;
$rootScope.fromScope = 'Content from root scope';
open({
template: '<div>{{fromScope}}</div>'
});
expect($document).toHaveModalOpenWithContent('Content from root scope', 'div');
});
it('should expose $resolve in template', function() {
open({
controller: function($scope) {},
resolve: {
$foo: function() {
return 'Content from resolve';
}
},
template: '<div>{{$resolve.$foo}}</div>'
});
expect($document).toHaveModalOpenWithContent('Content from resolve', 'div');
});
});
describe('keyboard', function () {
it('should not close modals if keyboard option is set to false', function() {
open({
template: '<div>No keyboard</div>',
keyboard: false
});
expect($document).toHaveModalsOpen(1);
triggerKeyDown($document, 27);
$rootScope.$digest();
expect($document).toHaveModalsOpen(1);
});
});
describe('backdrop', function() {
it('should not have any backdrop element if backdrop set to false', function() {
var modal = open({
template: '<div>No backdrop</div>',
backdrop: false
});
expect($document).toHaveModalOpenWithContent('No backdrop', 'div');
expect($document).not.toHaveBackdrop();
dismiss(modal);
expect($document).toHaveModalsOpen(0);
});
it('should not close modal on backdrop click if backdrop is specified as "static"', function() {
open({
template: '<div>Static backdrop</div>',
backdrop: 'static'
});
$document.find('body > div.modal-backdrop').click();
$rootScope.$digest();
expect($document).toHaveModalOpenWithContent('Static backdrop', 'div');
expect($document).toHaveBackdrop();
});
it('should contain backdrop in classes on each modal opening', function() {
var modal = open({ template: '<div>With backdrop</div>' });
var backdropEl = $document.find('body > div.modal-backdrop');
expect(backdropEl).toHaveClass('in');
dismiss(modal);
modal = open({ template: '<div>With backdrop</div>' });
backdropEl = $document.find('body > div.modal-backdrop');
expect(backdropEl).toHaveClass('in');
});
describe('custom backdrop classes', function () {
it('should support additional backdrop class as string', function() {
open({
template: '<div>With custom backdrop class</div>',
backdropClass: 'additional'
});
expect($document.find('div.modal-backdrop')).toHaveClass('additional');
});
});
});
describe('custom window classes', function() {
it('should support additional window class as string', function() {
open({
template: '<div>With custom window class</div>',
windowClass: 'additional'
});
expect($document.find('div.modal')).toHaveClass('additional');
});
});
describe('top window class', function () {
it('should support top class option', function () {
open({
template: '<div>With custom window top class</div>',
windowTopClass: 'top-class'
});
expect($document.find('div.modal')).toHaveClass('top-class');
});
});
describe('size', function() {
it('should support creating small modal dialogs', function() {
open({
template: '<div>Small modal dialog</div>',
size: 'sm'
});
expect($document.find('div.modal-dialog')).toHaveClass('modal-sm');
});
it('should support creating large modal dialogs', function() {
open({
template: '<div>Large modal dialog</div>',
size: 'lg'
});
expect($document.find('div.modal-dialog')).toHaveClass('modal-lg');
});
it('should support custom size modal dialogs', function() {
open({
template: '<div>Large modal dialog</div>',
size: 'custom'
});
expect($document.find('div.modal-dialog')).toHaveClass('modal-custom');
});
});
describe('animation', function() {
it('should have animation fade classes by default', function() {
open({
template: '<div>Small modal dialog</div>'
});
expect($document.find('.modal')).toHaveClass('fade');
expect($document.find('.modal-backdrop')).toHaveClass('fade');
});
it('should not have fade classes if animation false', function() {
open({
template: '<div>Small modal dialog</div>',
animation: false
});
expect($document.find('.modal')).not.toHaveClass('fade');
expect($document.find('.modal-backdrop')).not.toHaveClass('fade');
});
});
describe('appendTo', function() {
it('should be added to body by default', function() {
var modal = open({template: '<div>Content</div>'});
expect($document).toHaveModalsOpen(1);
expect($document).toHaveModalOpenWithContent('Content', 'div');
});
it('should not be added to body if appendTo is passed', function() {
var element = angular.element('<section>Some content</section>');
angular.element(document.body).append(element);
var modal = open({template: '<div>Content</div>', appendTo: element});
expect($document).not.toHaveModalOpenWithContent('Content', 'div');
element.remove();
});
it('should be added to appendTo element if appendTo is passed', function() {
var element = angular.element('<section>Some content</section>');
angular.element(document.body).append(element);
expect($document.find('section').children('div.modal').length).toBe(0);
open({template: '<div>Content</div>', appendTo: element});
expect($document.find('section').children('div.modal').length).toBe(1);
element.remove();
});
it('should throw error if appendTo element is not found', function() {
expect(function(){
open({template: '<div>Content</div>', appendTo: $document.find('aside')});
}).toThrow(new Error('appendTo element not found. Make sure that the element passed is in DOM.'));
});
it('should be removed from appendTo element when dismissed', function() {
var modal = open({template: '<div>Content</div>'});
expect($document).toHaveModalsOpen(1);
dismiss(modal);
expect($document)