Skip to content

Commit 9711084

Browse files
committed
About modal directive and unit tests
1 parent f83713e commit 9711084

File tree

6 files changed

+381
-0
lines changed

6 files changed

+381
-0
lines changed

Gruntfile.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,11 @@ module.exports = function (grunt) {
191191
src: ['filters/**/*.html'],
192192
dest: 'templates/filters.js'
193193
},
194+
'patternfly.modals': {
195+
cwd: 'src/',
196+
src: ['modals/**/*.html'],
197+
dest: 'templates/modals.js'
198+
},
194199
'patternfly.sort': {
195200
cwd: 'src/',
196201
src: ['sort/**/*.html'],
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
/**
2+
* @ngdoc directive
3+
* @name patternfly.modals.directive:pfAboutModal
4+
*
5+
* @description
6+
* Directive for rendering modal windows.
7+
*
8+
* @param {string=} additionalInfo Text explaining the version or copyright
9+
* @param {string=} copyright Product copyright information
10+
* @param {string=} imgAlt The alt text for the corner grahpic
11+
* @param {string=} imgSrc The source for the corner grahpic
12+
* @param {boolean=} isOpen Flag indicating that the modal should be opened
13+
* @param {function=} onClose Function to call when modal is closed
14+
* @param {object=} productInfo data for the modal:<br/>
15+
* <ul style='list-style-type: none'>
16+
* <li>.product - the product label
17+
* <li>.version - the product version
18+
* </ul>
19+
* @param {string=} title The product title for the modal
20+
*
21+
* @example
22+
<example module="patternfly.modals">
23+
<file name="index.html">
24+
<div ng-controller="ModalCtrl">
25+
<button ng-click="openSimple()" class="btn btn-default">Simple Modal</button>
26+
<button ng-click="openCustom()" class="btn btn-default">Custom Modal</button>
27+
<!-- Simple modal example -->
28+
<div pf-about-modal is-open="isSimpleOpen" on-close="onSimpleClose()" product-info="productInfo"
29+
title="title" copyright="copyright" img-alt="imgAlt" img-src="imgSrc"></div>
30+
<!-- Custom modal example -->
31+
<div pf-about-modal is-open="isCustomOpen" on-close="onCustomClose()" additional-info="additionalInfo"
32+
title="title" copyright="copyright" img-alt="imgAlt" img-src="imgSrc">
33+
<ul class="list-unstyled">
34+
<li><strong>Version</strong> 1.0.0.0.20160819142038_51be77c</li>
35+
<li><strong>Server Name</strong> Localhost</li>
36+
<li><strong>User Name</strong> admin</li>
37+
<li><strong>User Role</strong> Administrator</li>
38+
</ul>
39+
</div>
40+
</div>
41+
</file>
42+
<file name="script.js">
43+
angular.module('patternfly.modals').controller('ModalCtrl', function ($scope) {
44+
$scope.additionalInfo = "Donec consequat dignissim neque, sed suscipit quam egestas in. Fusce bibendum " +
45+
"laoreet lectus commodo interdum. Vestibulum odio ipsum, tristique et ante vel, iaculis placerat nulla. " +
46+
"Suspendisse iaculis urna feugiat lorem semper, ut iaculis risus tempus.";
47+
$scope.copyright = "Trademark and Copyright Information";
48+
$scope.imgAlt = "Patternfly Symbol";
49+
$scope.imgSrc = "img/logo-alt.svg";
50+
$scope.title = "Product Title";
51+
$scope.productInfo = [
52+
{ name: 'Label', value: 'Version' },
53+
{ name: 'Label', value: 'Version' },
54+
{ name: 'Label', value: 'Version' },
55+
{ name: 'Label', value: 'Version' },
56+
{ name: 'Label', value: 'Version' },
57+
{ name: 'Label', value: 'Version' }];
58+
$scope.openSimple = function () {
59+
$scope.isSimpleOpen = true;
60+
}
61+
$scope.onSimpleClose = function() {
62+
$scope.isSimpleOpen = false;
63+
}
64+
$scope.openCustom = function () {
65+
$scope.isCustomOpen = true;
66+
}
67+
$scope.onCustomClose = function() {
68+
$scope.isCustomOpen = false;
69+
}
70+
});
71+
</file>
72+
</example>
73+
*/
74+
angular.module('patternfly.modals')
75+
76+
.directive("pfAboutModalTransclude", function ($parse) {
77+
'use strict';
78+
return {
79+
link: function (scope, element, attrs) {
80+
element.append($parse(attrs.pfAboutModalTransclude)(scope));
81+
}
82+
};
83+
})
84+
85+
.directive('pfAboutModal', function () {
86+
'use strict';
87+
return {
88+
restrict: 'A',
89+
scope: {
90+
additionalInfo: '=?',
91+
copyright: '=?',
92+
close: "&onClose",
93+
imgAlt: '=?',
94+
imgSrc: '=?',
95+
isOpen: '=?',
96+
productInfo: '=',
97+
title: '=?'
98+
},
99+
templateUrl: 'modals/about-modal.html',
100+
transclude: true,
101+
controller: ['$scope', '$modal', '$transclude', function ($scope, $modal, $transclude) {
102+
if ($scope.isOpen === undefined) {
103+
$scope.isOpen = false;
104+
}
105+
106+
// The ui-bootstrap modal only supports either template or templateUrl as a way to specify the content.
107+
// When the content is retrieved, it is compiled and linked against the provided scope by the $modal service.
108+
// Unfortunately, there is no way to provide transclusion there.
109+
//
110+
// The solution below embeds a placeholder directive (i.e., pfAboutModalTransclude) to append the transcluded DOM.
111+
// The transcluded DOM is from a different location than the modal, so it needs to be handed over to the
112+
// placeholder directive. Thus, we're passing the actual DOM, not the parsed HTML.
113+
$scope.openModal = function () {
114+
$modal.open({
115+
controller: ['$scope', '$modalInstance', 'content', function ($scope, $modalInstance, content) {
116+
$scope.template = content;
117+
$scope.close = function () {
118+
$modalInstance.close();
119+
};
120+
$scope.$watch(
121+
function () {
122+
return $scope.isOpen;
123+
},
124+
function (newValue) {
125+
if (newValue === false) {
126+
$modalInstance.close();
127+
}
128+
}
129+
);
130+
}],
131+
resolve: {
132+
content: function () {
133+
var transcludedContent;
134+
$transclude(function (clone) {
135+
transcludedContent = clone;
136+
});
137+
return transcludedContent;
138+
}
139+
},
140+
scope: $scope,
141+
templateUrl: "about-modal-template.html"
142+
})
143+
.result.then(
144+
function () {
145+
$scope.close(); // closed
146+
},
147+
function () {
148+
$scope.close(); // dismissed
149+
}
150+
);
151+
};
152+
}],
153+
link: function (scope, element, attrs) {
154+
// watching isOpen attribute to dispay modal when needed
155+
var isOpenListener = scope.$watch('isOpen', function (newVal, oldVal) {
156+
if (newVal === true) {
157+
scope.openModal();
158+
}
159+
});
160+
scope.$on('$destroy', isOpenListener);
161+
}
162+
};
163+
});

src/modals/about-modal.html

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<script type="text/ng-template" id="about-modal-template.html">
2+
<div class="about-modal-pf">
3+
<div class="modal-header">
4+
<button type="button" class="close" ng-click="close()" aria-hidden="true">
5+
<span class="pficon pficon-close"></span>
6+
</button>
7+
</div>
8+
<div class="modal-body">
9+
<h1 ng-if="title">{{title}}</h1>
10+
<div ng-if="productInfo && productInfo.length > 0" class="product-versions-pf">
11+
<ul class="list-unstyled">
12+
<li ng-repeat="info in productInfo"><strong>{{info.name}}</strong> {{info.value}}</li>
13+
</ul>
14+
</div>
15+
<div pf-about-modal-transclude="template" class="product-versions-pf"></div>
16+
<div ng-if="additionalInfo" class="product-versions-pf">{{additionalInfo}}</div>
17+
<div ng-if="copyright" class="trademark-pf">{{copyright}}</div>
18+
</div>
19+
<div class="modal-footer">
20+
<img ng-if="imgSrc" ng-src="{{imgSrc}}" alt="{{imgAlt}}"/>
21+
</div>
22+
</div>
23+
</script>

src/modals/modals.module.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* @name patternfly
3+
*
4+
* @description
5+
* Modal module for patternfly.
6+
*
7+
*/
8+
angular.module('patternfly.modals', ['ui.bootstrap.modal', 'ui.bootstrap.tpls']);

src/patternfly.module.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ angular.module('patternfly', [
99
'patternfly.card',
1010
'patternfly.filters',
1111
'patternfly.form',
12+
'patternfly.modals',
1213
'patternfly.navigation',
1314
'patternfly.notification',
1415
'patternfly.select',

test/modals/about-modal.spec.js

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
describe('Directive: pfABoutModal', function () {
2+
var $scope;
3+
var $compile;
4+
5+
// load the controller's module
6+
beforeEach(module(
7+
'patternfly.modals',
8+
'modals/about-modal.html'
9+
));
10+
11+
beforeEach(inject(function (_$compile_, _$rootScope_) {
12+
$compile = _$compile_;
13+
$scope = _$rootScope_;
14+
}));
15+
16+
var compileHtml = function (markup, scope) {
17+
var element = angular.element(markup);
18+
$compile(element)(scope);
19+
scope.$digest();
20+
return element;
21+
};
22+
23+
var closeModal = function(scope) {
24+
scope.isOpen = false;
25+
scope.$digest();
26+
27+
// Although callbacks are executed properly, the modal is not removed in this
28+
// environment -- must remove it manually to mimic UI Bootstrap.
29+
var modal = getModal();
30+
if (modal) {
31+
modal.remove();
32+
}
33+
var modalBackdrop = angular.element(document.querySelector('.modal-backdrop'));
34+
if (modalBackdrop) {
35+
modalBackdrop.remove();
36+
}
37+
};
38+
39+
// Modal elements are located in a template, so wait until modal is shown.
40+
var getModal = function () {
41+
return angular.element(document.querySelector('.modal'));
42+
};
43+
44+
var openModal = function(scope) {
45+
scope.isOpen = true;
46+
scope.$digest();
47+
};
48+
49+
beforeEach(function () {
50+
closeModal($scope);
51+
$scope.copyright = "Copyright Information";
52+
$scope.imgAlt = "Patternfly Symbol";
53+
$scope.imgSrc = "img/logo-alt.svg";
54+
$scope.title = "Product Title";
55+
$scope.isOpen = true;
56+
$scope.productInfo = [
57+
{ product: 'Label', version: 'Version' },
58+
{ product: 'Label', version: 'Version' },
59+
{ product: 'Label', version: 'Version' },
60+
{ product: 'Label', version: 'Version' },
61+
{ product: 'Label', version: 'Version' },
62+
{ product: 'Label', version: 'Version' },
63+
{ product: 'Label', version: 'Version' }];
64+
$scope.open = function () {
65+
$scope.isOpen = true;
66+
}
67+
$scope.onClose = function() {
68+
$scope.isOpen = false;
69+
}
70+
});
71+
72+
it('should invoke the onClose callback when close button is clicked', function () {
73+
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>';
74+
compileHtml(modalHtml, $scope);
75+
var closeButton = angular.element(getModal()).find('button');
76+
eventFire(closeButton[0], 'click');
77+
$scope.$digest();
78+
expect($scope.isOpen).toBe(false);
79+
});
80+
81+
it('should open the about modal via an external button click', function () {
82+
$scope.isOpen = false;
83+
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>';
84+
compileHtml(modalHtml, $scope);
85+
var buttonHtml = '<button ng-click="open()" class="btn btn-default">Launch about modal</button>';
86+
var closeButton = compileHtml(buttonHtml, $scope);
87+
eventFire(closeButton[0], 'click');
88+
$scope.$digest();
89+
expect($scope.isOpen).toBe(true);
90+
expect(angular.element(getModal()).find('h1').length).toBe(1);
91+
});
92+
93+
it('should open the about modal programmatically', function () {
94+
$scope.isOpen = false;
95+
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>';
96+
compileHtml(modalHtml, $scope);
97+
expect(angular.element(getModal()).find('h1').length).toBe(0);
98+
openModal($scope);
99+
expect(angular.element(getModal()).find('h1').length).toBe(1);
100+
});
101+
102+
it('should not open the about modal', function () {
103+
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>';
104+
compileHtml(modalHtml, $scope);
105+
expect(angular.element(getModal()).find('h1').length).toBe(0);
106+
expect(angular.element(getModal()).find('.trademark-pf').length).toBe(0);
107+
});
108+
109+
it('should set the product title', function () {
110+
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>';
111+
compileHtml(modalHtml, $scope);
112+
expect(angular.element(getModal()).find('h1').html()).toBe('Product Title');
113+
});
114+
115+
it('should not show product title when a title is not supplied', function () {
116+
var modalHtml = '<div pf-about-modal is-open="isOpen" on-close="onClose()" copyright="copyright" img-alt="imgAlt" img-src="imgSrc" product-info="productInfo"></div>';
117+
compileHtml(modalHtml, $scope);
118+
expect(angular.element(getModal()).find('h1').length).toBe(0);
119+
});
120+
121+
it('should set the product copyright', function () {
122+
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>';
123+
compileHtml(modalHtml, $scope);
124+
expect(angular.element(getModal()).find('.trademark-pf').html()).toBe('Copyright Information');
125+
});
126+
127+
it('should not show product copyright when a copyright is not supplied', function () {
128+
var modalHtml = '<div pf-about-modal is-open="isOpen" on-close="onClose()" title="title" img-alt="imgAlt" img-src="imgSrc" product-info="productInfo"></div>';
129+
compileHtml(modalHtml, $scope);
130+
expect(angular.element(getModal()).find('.trademark-pf').length).toBe(0);
131+
});
132+
133+
it('should set the corner graphic alt text', function () {
134+
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>';
135+
compileHtml(modalHtml, $scope);
136+
var footer = angular.element(getModal()).find('.modal-footer');
137+
expect(angular.element(footer).find('img').attr('alt')).toBe('Patternfly Symbol');
138+
});
139+
140+
it('should not show alt text for corner graphic when imgAlt is not supplied', function () {
141+
var modalHtml = '<div pf-about-modal is-open="isOpen" on-close="onClose()" title="title" copyright="copyright" img-src="imgSrc" product-info="productInfo"></div>';
142+
compileHtml(modalHtml, $scope);
143+
var footer = angular.element(getModal()).find('.modal-footer');
144+
expect(angular.element(footer).find('img').attr('alt').length).toBe(0);
145+
});
146+
147+
it('should set the corner graphic src', function () {
148+
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>';
149+
compileHtml(modalHtml, $scope);
150+
var footer = angular.element(getModal()).find('.modal-footer');
151+
expect(angular.element(footer).find('img').attr('src')).toBe('img/logo-alt.svg');
152+
});
153+
154+
it('should not show corner graphic when imgSrc is not supplied', function () {
155+
var modalHtml = '<div pf-about-modal is-open="isOpen" on-close="onClose()" title="title" copyright="copyright" img-alt="imgAlt" product-info="productInfo"></div>';
156+
compileHtml(modalHtml, $scope);
157+
var footer = angular.element(getModal()).find('.modal-footer');
158+
expect(angular.element(footer).find('img').length).toBe(0);
159+
});
160+
161+
it('should show simple content', function () {
162+
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>';
163+
compileHtml(modalHtml, $scope);
164+
var transclude = angular.element(getModal()).find('.product-versions-pf');
165+
expect(angular.element(transclude).find('ul').length).toBe(1);
166+
});
167+
168+
it('should show custom content', function () {
169+
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>';
170+
compileHtml(modalHtml, $scope);
171+
var transclude = angular.element(getModal()).find('.product-versions-pf');
172+
expect(angular.element(transclude).find('ul').length).toBe(1);
173+
});
174+
175+
it('should not show content', function () {
176+
var modalHtml = '<div pf-about-modal is-open="isOpen" on-close="onClose()" title="title" copyright="copyright" img-alt="imgAlt" img-src="imgSrc"></div>';
177+
compileHtml(modalHtml, $scope);
178+
var transclude = angular.element(getModal()).find('.product-versions-pf');
179+
expect(angular.element(transclude).find('ul').length).toBe(0);
180+
});
181+
});

0 commit comments

Comments
 (0)