Skip to content

Commit

Permalink
Add ability to pass a title and show the close button on a modal (#10069
Browse files Browse the repository at this point in the history
)

* Add ability to pass a title and show the close button on a modal

* address code review comments

- Clean up tests and add some new ones
- Default onClose to onCancel if nothing is supplied.
  • Loading branch information
stacey-gammon authored Jan 28, 2017
1 parent 5421e2f commit 58b5cbd
Show file tree
Hide file tree
Showing 3 changed files with 220 additions and 1 deletion.
186 changes: 186 additions & 0 deletions src/ui/public/modals/__tests__/confirm_modal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import angular from 'angular';
import expect from 'expect.js';
import ngMock from 'ng_mock';
import sinon from 'sinon';
import _ from 'lodash';

describe('ui/modals/confirm_modal', function () {
let confirmModal;
let $rootScope;

beforeEach(function () {
ngMock.module('kibana');
ngMock.inject(function ($injector) {
confirmModal = $injector.get('confirmModal');
$rootScope = $injector.get('$rootScope');
});
});

function findByDataTestSubj(dataTestSubj) {
return angular.element(document.body).find(`[data-test-subj=${dataTestSubj}]`);
}

afterEach(function () {
const confirmButton = findByDataTestSubj('confirmModalConfirmButton');
if (confirmButton) {
angular.element(confirmButton).click();
}
});

describe('throws an exception', function () {
it('when no custom confirm button passed', function () {
expect(() => confirmModal('hi', { onConfirm: _.noop })).to.throwError();
});

it('when no custom noConfirm function is passed', function () {
expect(() => confirmModal('hi', { confirmButtonText: 'bye' })).to.throwError();
});

it('when showClose is on but title is not given', function () {
const options = { customConfirmButton: 'b', onConfirm: _.noop, showClose: true };
expect(() => confirmModal('hi', options)).to.throwError();
});
});

it('shows the message', function () {
const myMessage = 'Hi, how are you?';
confirmModal(myMessage, { confirmButtonText: 'GREAT!', onConfirm: _.noop });

$rootScope.$digest();
const message = findByDataTestSubj('confirmModalBodyText')[0].innerText;
expect(message).to.equal(myMessage);
});

describe('shows custom text', function () {
const confirmModalOptions = {
confirmButtonText: 'Troodon',
cancelButtonText: 'Dilophosaurus',
title: 'Dinosaurs',
onConfirm: _.noop
};

it('for confirm button', () => {
confirmModal('What\'s your favorite dinosaur?', confirmModalOptions);
$rootScope.$digest();
const confirmButtonText = findByDataTestSubj('confirmModalConfirmButton')[0].innerText;
expect(confirmButtonText).to.equal('Troodon');
});

it('for cancel button', () => {
confirmModal('What\'s your favorite dinosaur?', confirmModalOptions);
$rootScope.$digest();
const cancelButtonText = findByDataTestSubj('confirmModalCancelButton')[0].innerText;
expect(cancelButtonText).to.equal('Dilophosaurus');
});

it('for title text', () => {
confirmModal('What\'s your favorite dinosaur?', confirmModalOptions);
$rootScope.$digest();
const titleText = findByDataTestSubj('confirmModalTitleText')[0].innerText;
expect(titleText).to.equal('Dinosaurs');
});
});

describe('x icon', function () {
it('is visible when showClose is true', function () {
const confirmModalOptions = {
confirmButtonText: 'bye',
onConfirm: _.noop,
showClose: true,
title: 'hi'
};
confirmModal('hi', confirmModalOptions);

$rootScope.$digest();
const xIcon = findByDataTestSubj('confirmModalCloseButton');
expect(xIcon.length).to.be(1);
});

it('is not visible when showClose is false', function () {
const confirmModalOptions = {
confirmButtonText: 'bye',
onConfirm: _.noop,
title: 'hi',
showClose: false
};
confirmModal('hi', confirmModalOptions);

$rootScope.$digest();
const xIcon = findByDataTestSubj('confirmModalCloseButton');
expect(xIcon.length).to.be(0);
});
});

describe('callbacks are called:', function () {
const confirmCallback = sinon.spy();
const closeCallback = sinon.spy();
const cancelCallback = sinon.spy();

const confirmModalOptions = {
confirmButtonText: 'bye',
onConfirm: confirmCallback,
onCancel: cancelCallback,
onClose: closeCallback,
title: 'hi',
showClose: true
};

function resetSpyCounts() {
confirmCallback.reset();
closeCallback.reset();
cancelCallback.reset();
}

it('onClose', function () {
resetSpyCounts();
confirmModal('hi', confirmModalOptions);
$rootScope.$digest();
findByDataTestSubj('confirmModalCloseButton').click();

expect(closeCallback.called).to.be(true);
expect(confirmCallback.called).to.be(false);
expect(cancelCallback.called).to.be(false);
});

it('onCancel', function () {
resetSpyCounts();
confirmModal('hi', confirmModalOptions);
$rootScope.$digest();
findByDataTestSubj('confirmModalCancelButton').click();

expect(closeCallback.called).to.be(false);
expect(confirmCallback.called).to.be(false);
expect(cancelCallback.called).to.be(true);
});

it('onConfirm', function () {
resetSpyCounts();
confirmModal('hi', confirmModalOptions);
$rootScope.$digest();
findByDataTestSubj('confirmModalConfirmButton').click();

expect(closeCallback.called).to.be(false);
expect(confirmCallback.called).to.be(true);
expect(cancelCallback.called).to.be(false);
});


it('onClose defaults to onCancel if not specified', function () {
resetSpyCounts();
const confirmModalOptions = {
confirmButtonText: 'bye',
onConfirm: confirmCallback,
onCancel: cancelCallback,
title: 'hi',
showClose: true
};

confirmModal('hi', confirmModalOptions);
$rootScope.$digest();
findByDataTestSubj('confirmModalCloseButton').click();

expect(confirmCallback.called).to.be(false);
expect(cancelCallback.called).to.be(true);
});
});
});
12 changes: 12 additions & 0 deletions src/ui/public/modals/confirm_modal.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
<div class="kuiModal" style="width: 450px" data-test-subj="confirmModal">
<div class="kuiModalHeader" ng-if="title">
<div class="kuiModalHeader__title" data-test-subj="confirmModalTitleText">
{{title}}
</div>

<div
ng-if="showClose"
class="kuiModalHeaderCloseButton kuiIcon fa-times"
data-test-subj="confirmModalCloseButton"
ng-click="onClose()"
></div>
</div>
<div class="kuiModalBody">
<div
class="kuiModalBodyText"
Expand Down
23 changes: 22 additions & 1 deletion src/ui/public/modals/confirm_modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ const module = uiModules.get('kibana');
* @property {String=} cancelButtonText
* @property {function} onConfirm
* @property {function=} onCancel
* @property {String=} title - If given, shows a title on the confirm modal. A title must be given if
* showClose is true, for aesthetic reasons.
* @property {Boolean=} showClose - If true, shows an [x] icon close button which by default is a noop
* @property {function=} onClose - Custom close button to call if showClose is true. If not supplied
* but showClose is true, the function defaults to onCancel.
*/

module.factory('confirmModal', function ($rootScope, $compile) {
Expand All @@ -23,14 +28,24 @@ module.factory('confirmModal', function ($rootScope, $compile) {
return function confirmModal(message, customOptions) {
const defaultOptions = {
onCancel: noop,
cancelButtonText: 'Cancel'
cancelButtonText: 'Cancel',
showClose: false
};

if (customOptions.showClose === true && !customOptions.title) {
throw new Error('A title must be supplied when a close icon is shown');
}

if (!customOptions.confirmButtonText || !customOptions.onConfirm) {
throw new Error('Please specify confirmation button text and onConfirm action');
}

const options = Object.assign(defaultOptions, customOptions);

// Special handling for onClose - if no specific callback was supplied, default to the
// onCancel callback.
options.onClose = customOptions.onClose || options.onCancel;

if (modalPopover) {
throw new Error('You\'ve called confirmModal but there\'s already a modal open. ' +
'You can only have one modal open at a time.');
Expand All @@ -41,6 +56,8 @@ module.factory('confirmModal', function ($rootScope, $compile) {
confirmScope.message = message;
confirmScope.confirmButtonText = options.confirmButtonText;
confirmScope.cancelButtonText = options.cancelButtonText;
confirmScope.title = options.title;
confirmScope.showClose = options.showClose;
confirmScope.onConfirm = () => {
destroy();
options.onConfirm();
Expand All @@ -49,6 +66,10 @@ module.factory('confirmModal', function ($rootScope, $compile) {
destroy();
options.onCancel();
};
confirmScope.onClose = () => {
destroy();
options.onClose();
};

const modalInstance = $compile(template)(confirmScope);
modalPopover = new ModalOverlay(modalInstance);
Expand Down

0 comments on commit 58b5cbd

Please sign in to comment.