UNPKG

angular-ui-bootstrap

Version:

Native AngularJS (Angular) directives for Bootstrap

1,455 lines (1,167 loc) 71.8 kB
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)