-
Notifications
You must be signed in to change notification settings - Fork 90
About modal directive and unit tests #299
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
jeff-phillips-18
merged 1 commit into
patternfly:master
from
dlabrecq:PTNFLY-1011-directive
Aug 24, 2016
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
/** | ||
* @ngdoc directive | ||
* @name patternfly.modals.directive:pfAboutModal | ||
* | ||
* @description | ||
* Directive for rendering modal windows. | ||
* | ||
* @param {string=} additionalInfo Text explaining the version or copyright | ||
* @param {string=} copyright Product copyright information | ||
* @param {string=} imgAlt The alt text for the corner grahpic | ||
* @param {string=} imgSrc The source for the corner grahpic | ||
* @param {boolean=} isOpen Flag indicating that the modal should be opened | ||
* @param {function=} onClose Function to call when modal is closed | ||
* @param {object=} productInfo data for the modal:<br/> | ||
* <ul style='list-style-type: none'> | ||
* <li>.product - the product label | ||
* <li>.version - the product version | ||
* </ul> | ||
* @param {string=} title The product title for the modal | ||
* | ||
* @example | ||
<example module="patternfly.modals"> | ||
<file name="index.html"> | ||
<div ng-controller="ModalCtrl"> | ||
<button ng-click="open()" class="btn btn-default">Launch About Modal</button> | ||
<div pf-about-modal is-open="isOpen" on-close="onClose()" additional-info="additionalInfo" | ||
product-info="productInfo" title="title" copyright="copyright" img-alt="imgAlt" img-src="imgSrc"></div> | ||
</div> | ||
</file> | ||
<file name="script.js"> | ||
angular.module('patternfly.modals').controller('ModalCtrl', function ($scope) { | ||
$scope.additionalInfo = "Donec consequat dignissim neque, sed suscipit quam egestas in. Fusce bibendum " + | ||
"laoreet lectus commodo interdum. Vestibulum odio ipsum, tristique et ante vel, iaculis placerat nulla. " + | ||
"Suspendisse iaculis urna feugiat lorem semper, ut iaculis risus tempus."; | ||
$scope.copyright = "Trademark and Copyright Information"; | ||
$scope.imgAlt = "Patternfly Symbol"; | ||
$scope.imgSrc = "img/logo-alt.svg"; | ||
$scope.title = "Product Title"; | ||
$scope.productInfo = [ | ||
{ name: 'Version', value: '1.0.0.0.20160819142038_51be77c' }, | ||
{ name: 'Server Name', value: 'Localhost' }, | ||
{ name: 'User Name', value: 'admin' }, | ||
{ name: 'User Role', value: 'Administrator' }]; | ||
$scope.open = function () { | ||
$scope.isOpen = true; | ||
} | ||
$scope.onClose = function() { | ||
$scope.isOpen = false; | ||
} | ||
}); | ||
</file> | ||
</example> | ||
*/ | ||
angular.module('patternfly.modals') | ||
|
||
.directive("pfAboutModalTransclude", function ($parse) { | ||
'use strict'; | ||
return { | ||
link: function (scope, element, attrs) { | ||
element.append($parse(attrs.pfAboutModalTransclude)(scope)); | ||
} | ||
}; | ||
}) | ||
|
||
.directive('pfAboutModal', function () { | ||
'use strict'; | ||
return { | ||
restrict: 'A', | ||
scope: { | ||
additionalInfo: '=?', | ||
copyright: '=?', | ||
close: "&onClose", | ||
imgAlt: '=?', | ||
imgSrc: '=?', | ||
isOpen: '=?', | ||
productInfo: '=', | ||
title: '=?' | ||
}, | ||
templateUrl: 'modals/about-modal.html', | ||
transclude: true, | ||
controller: ['$scope', '$modal', '$transclude', function ($scope, $modal, $transclude) { | ||
if ($scope.isOpen === undefined) { | ||
$scope.isOpen = false; | ||
} | ||
|
||
// The ui-bootstrap modal only supports either template or templateUrl as a way to specify the content. | ||
// When the content is retrieved, it is compiled and linked against the provided scope by the $modal service. | ||
// Unfortunately, there is no way to provide transclusion there. | ||
// | ||
// The solution below embeds a placeholder directive (i.e., pfAboutModalTransclude) to append the transcluded DOM. | ||
// The transcluded DOM is from a different location than the modal, so it needs to be handed over to the | ||
// placeholder directive. Thus, we're passing the actual DOM, not the parsed HTML. | ||
$scope.openModal = function () { | ||
$modal.open({ | ||
controller: ['$scope', '$modalInstance', 'content', function ($scope, $modalInstance, content) { | ||
$scope.template = content; | ||
$scope.close = function () { | ||
$modalInstance.close(); | ||
}; | ||
$scope.$watch( | ||
function () { | ||
return $scope.isOpen; | ||
}, | ||
function (newValue) { | ||
if (newValue === false) { | ||
$modalInstance.close(); | ||
} | ||
} | ||
); | ||
}], | ||
resolve: { | ||
content: function () { | ||
var transcludedContent; | ||
$transclude(function (clone) { | ||
transcludedContent = clone; | ||
}); | ||
return transcludedContent; | ||
} | ||
}, | ||
scope: $scope, | ||
templateUrl: "about-modal-template.html" | ||
}) | ||
.result.then( | ||
function () { | ||
$scope.close(); // closed | ||
}, | ||
function () { | ||
$scope.close(); // dismissed | ||
} | ||
); | ||
}; | ||
}], | ||
link: function (scope, element, attrs) { | ||
// watching isOpen attribute to dispay modal when needed | ||
var isOpenListener = scope.$watch('isOpen', function (newVal, oldVal) { | ||
if (newVal === true) { | ||
scope.openModal(); | ||
} | ||
}); | ||
scope.$on('$destroy', isOpenListener); | ||
} | ||
}; | ||
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
<script type="text/ng-template" id="about-modal-template.html"> | ||
<div class="about-modal-pf"> | ||
<div class="modal-header"> | ||
<button type="button" class="close" ng-click="close()" aria-hidden="true"> | ||
<span class="pficon pficon-close"></span> | ||
</button> | ||
</div> | ||
<div class="modal-body"> | ||
<h1 ng-if="title">{{title}}</h1> | ||
<div ng-if="productInfo && productInfo.length > 0" class="product-versions-pf"> | ||
<ul class="list-unstyled"> | ||
<li ng-repeat="info in productInfo"><strong>{{info.name}}</strong> {{info.value}}</li> | ||
</ul> | ||
</div> | ||
<div pf-about-modal-transclude="template" class="product-versions-pf"></div> | ||
<div ng-if="additionalInfo" class="product-versions-pf">{{additionalInfo}}</div> | ||
<div ng-if="copyright" class="trademark-pf">{{copyright}}</div> | ||
</div> | ||
<div class="modal-footer"> | ||
<img ng-if="imgSrc" ng-src="{{imgSrc}}" alt="{{imgAlt}}"/> | ||
</div> | ||
</div> | ||
</script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
/** | ||
* @name patternfly | ||
* | ||
* @description | ||
* Modal module for patternfly. | ||
* | ||
*/ | ||
angular.module('patternfly.modals', ['ui.bootstrap.modal', 'ui.bootstrap.tpls']); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
describe('Directive: pfABoutModal', function () { | ||
var $scope; | ||
var $compile; | ||
|
||
// load the controller's module | ||
beforeEach(module( | ||
'patternfly.modals', | ||
'modals/about-modal.html' | ||
)); | ||
|
||
beforeEach(inject(function (_$compile_, _$rootScope_) { | ||
$compile = _$compile_; | ||
$scope = _$rootScope_; | ||
})); | ||
|
||
var compileHtml = function (markup, scope) { | ||
var element = angular.element(markup); | ||
$compile(element)(scope); | ||
scope.$digest(); | ||
return element; | ||
}; | ||
|
||
var closeModal = function(scope) { | ||
scope.isOpen = false; | ||
scope.$digest(); | ||
|
||
// Although callbacks are executed properly, the modal is not removed in this | ||
// environment -- must remove it manually to mimic UI Bootstrap. | ||
var modal = getModal(); | ||
if (modal) { | ||
modal.remove(); | ||
} | ||
var modalBackdrop = angular.element(document.querySelector('.modal-backdrop')); | ||
if (modalBackdrop) { | ||
modalBackdrop.remove(); | ||
} | ||
}; | ||
|
||
// Modal elements are located in a template, so wait until modal is shown. | ||
var getModal = function () { | ||
return angular.element(document.querySelector('.modal')); | ||
}; | ||
|
||
var openModal = function(scope) { | ||
scope.isOpen = true; | ||
scope.$digest(); | ||
}; | ||
|
||
beforeEach(function () { | ||
closeModal($scope); | ||
$scope.copyright = "Copyright Information"; | ||
$scope.imgAlt = "Patternfly Symbol"; | ||
$scope.imgSrc = "img/logo-alt.svg"; | ||
$scope.title = "Product Title"; | ||
$scope.isOpen = true; | ||
$scope.productInfo = [ | ||
{ product: 'Label', version: 'Version' }, | ||
{ product: 'Label', version: 'Version' }, | ||
{ product: 'Label', version: 'Version' }, | ||
{ product: 'Label', version: 'Version' }, | ||
{ product: 'Label', version: 'Version' }, | ||
{ product: 'Label', version: 'Version' }, | ||
{ product: 'Label', version: 'Version' }]; | ||
$scope.open = function () { | ||
$scope.isOpen = true; | ||
} | ||
$scope.onClose = function() { | ||
$scope.isOpen = false; | ||
} | ||
}); | ||
|
||
it('should invoke the onClose callback when close button is clicked', function () { | ||
var modalHtml = '<div pf-about-modal is-open="isOpen" on-close="onClose()" title="title" copyright="copyright" img-alt="imgAlt" img-src="imgSrc" product-info="productInfo"></div>'; | ||
compileHtml(modalHtml, $scope); | ||
var closeButton = angular.element(getModal()).find('button'); | ||
eventFire(closeButton[0], 'click'); | ||
$scope.$digest(); | ||
expect($scope.isOpen).toBe(false); | ||
}); | ||
|
||
it('should open the about modal via an external button click', function () { | ||
$scope.isOpen = false; | ||
var modalHtml = '<div pf-about-modal is-open="isOpen" on-close="onClose()" title="title" copyright="copyright" img-alt="imgAlt" img-src="imgSrc" product-info="productInfo"></div>'; | ||
compileHtml(modalHtml, $scope); | ||
var buttonHtml = '<button ng-click="open()" class="btn btn-default">Launch about modal</button>'; | ||
var closeButton = compileHtml(buttonHtml, $scope); | ||
eventFire(closeButton[0], 'click'); | ||
$scope.$digest(); | ||
expect($scope.isOpen).toBe(true); | ||
expect(angular.element(getModal()).find('h1').length).toBe(1); | ||
}); | ||
|
||
it('should open the about modal programmatically', function () { | ||
$scope.isOpen = false; | ||
var modalHtml = '<div pf-about-modal is-open="isOpen" on-close="onClose()" title="title" copyright="copyright" img-alt="imgAlt" img-src="imgSrc" product-info="productInfo"></div>'; | ||
compileHtml(modalHtml, $scope); | ||
expect(angular.element(getModal()).find('h1').length).toBe(0); | ||
openModal($scope); | ||
expect(angular.element(getModal()).find('h1').length).toBe(1); | ||
}); | ||
|
||
it('should not open the about modal', function () { | ||
var modalHtml = '<div pf-about-modal is-open="false" on-close="onClose()" title="title" copyright="copyright" img-alt="imgAlt" img-src="imgSrc" product-info="productInfo"></div>'; | ||
compileHtml(modalHtml, $scope); | ||
expect(angular.element(getModal()).find('h1').length).toBe(0); | ||
expect(angular.element(getModal()).find('.trademark-pf').length).toBe(0); | ||
}); | ||
|
||
it('should set the product title', function () { | ||
var modalHtml = '<div pf-about-modal is-open="isOpen" on-close="onClose()" title="title" copyright="copyright" img-alt="imgAlt" img-src="imgSrc" product-info="productInfo"></div>'; | ||
compileHtml(modalHtml, $scope); | ||
expect(angular.element(getModal()).find('h1').html()).toBe('Product Title'); | ||
}); | ||
|
||
it('should not show product title when a title is not supplied', function () { | ||
var modalHtml = '<div pf-about-modal is-open="isOpen" on-close="onClose()" copyright="copyright" img-alt="imgAlt" img-src="imgSrc" product-info="productInfo"></div>'; | ||
compileHtml(modalHtml, $scope); | ||
expect(angular.element(getModal()).find('h1').length).toBe(0); | ||
}); | ||
|
||
it('should set the product copyright', function () { | ||
var modalHtml = '<div pf-about-modal is-open="isOpen" on-close="onClose()" title="title" copyright="copyright" img-alt="imgAlt" img-src="imgSrc" product-info="productInfo"></div>'; | ||
compileHtml(modalHtml, $scope); | ||
expect(angular.element(getModal()).find('.trademark-pf').html()).toBe('Copyright Information'); | ||
}); | ||
|
||
it('should not show product copyright when a copyright is not supplied', function () { | ||
var modalHtml = '<div pf-about-modal is-open="isOpen" on-close="onClose()" title="title" img-alt="imgAlt" img-src="imgSrc" product-info="productInfo"></div>'; | ||
compileHtml(modalHtml, $scope); | ||
expect(angular.element(getModal()).find('.trademark-pf').length).toBe(0); | ||
}); | ||
|
||
it('should set the corner graphic alt text', function () { | ||
var modalHtml = '<div pf-about-modal is-open="isOpen" on-close="onClose()" title="title" copyright="copyright" img-alt="imgAlt" img-src="imgSrc" product-info="productInfo"></div>'; | ||
compileHtml(modalHtml, $scope); | ||
var footer = angular.element(getModal()).find('.modal-footer'); | ||
expect(angular.element(footer).find('img').attr('alt')).toBe('Patternfly Symbol'); | ||
}); | ||
|
||
it('should not show alt text for corner graphic when imgAlt is not supplied', function () { | ||
var modalHtml = '<div pf-about-modal is-open="isOpen" on-close="onClose()" title="title" copyright="copyright" img-src="imgSrc" product-info="productInfo"></div>'; | ||
compileHtml(modalHtml, $scope); | ||
var footer = angular.element(getModal()).find('.modal-footer'); | ||
expect(angular.element(footer).find('img').attr('alt').length).toBe(0); | ||
}); | ||
|
||
it('should set the corner graphic src', function () { | ||
var modalHtml = '<div pf-about-modal is-open="isOpen" on-close="onClose()" title="title" copyright="copyright" img-alt="imgAlt" img-src="imgSrc" product-info="productInfo"></div>'; | ||
compileHtml(modalHtml, $scope); | ||
var footer = angular.element(getModal()).find('.modal-footer'); | ||
expect(angular.element(footer).find('img').attr('src')).toBe('img/logo-alt.svg'); | ||
}); | ||
|
||
it('should not show corner graphic when imgSrc is not supplied', function () { | ||
var modalHtml = '<div pf-about-modal is-open="isOpen" on-close="onClose()" title="title" copyright="copyright" img-alt="imgAlt" product-info="productInfo"></div>'; | ||
compileHtml(modalHtml, $scope); | ||
var footer = angular.element(getModal()).find('.modal-footer'); | ||
expect(angular.element(footer).find('img').length).toBe(0); | ||
}); | ||
|
||
it('should show simple content', function () { | ||
var modalHtml = '<div pf-about-modal is-open="isOpen" on-close="onClose()" title="title" copyright="copyright" img-alt="imgAlt" img-src="imgSrc" product-info="productInfo"></div>'; | ||
compileHtml(modalHtml, $scope); | ||
var transclude = angular.element(getModal()).find('.product-versions-pf'); | ||
expect(angular.element(transclude).find('ul').length).toBe(1); | ||
}); | ||
|
||
it('should show custom content', function () { | ||
var modalHtml = '<div pf-about-modal is-open="isOpen" on-close="onClose()" title="title" copyright="copyright" img-alt="imgAlt" img-src="imgSrc"><ul class="list-unstyled"><li><strong>Label</strong> Version</li></ul></div>'; | ||
compileHtml(modalHtml, $scope); | ||
var transclude = angular.element(getModal()).find('.product-versions-pf'); | ||
expect(angular.element(transclude).find('ul').length).toBe(1); | ||
}); | ||
|
||
it('should not show content', function () { | ||
var modalHtml = '<div pf-about-modal is-open="isOpen" on-close="onClose()" title="title" copyright="copyright" img-alt="imgAlt" img-src="imgSrc"></div>'; | ||
compileHtml(modalHtml, $scope); | ||
var transclude = angular.element(getModal()).find('.product-versions-pf'); | ||
expect(angular.element(transclude).find('ul').length).toBe(0); | ||
}); | ||
}); |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should remove the $watch listener when the directive is destroyed:
var listener = $watch(...
$scope.$on('destroy', listener);