Merge "JSCS cleanup - horizon/static/framework conf and util"

This commit is contained in:
Jenkins 2015-06-19 14:51:04 +00:00 committed by Gerrit Code Review
commit bf9acb39a5
14 changed files with 581 additions and 607 deletions

View File

@ -1,10 +1,10 @@
/*global angular*/
(function () { (function () {
'use strict'; 'use strict';
angular.module('horizon.framework.conf', [])
angular
.module('horizon.framework.conf', [])
.constant('horizon.framework.conf.spinner_options', { .constant('horizon.framework.conf.spinner_options', {
inline: { inline: {
lines: 10, lines: 10,
length: 5, length: 5,
width: 2, width: 2,
@ -14,7 +14,7 @@
trail: 50, trail: 50,
zIndex: 100 zIndex: 100
}, },
modal: { modal: {
lines: 10, lines: 10,
length: 15, length: 15,
width: 4, width: 4,
@ -33,4 +33,4 @@
trail: 50 trail: 50
} }
}); });
}()); })();

View File

@ -1,39 +1,41 @@
(function () { (function () {
'use strict'; 'use strict';
angular.module('horizon.framework', [ angular
'horizon.framework.conf', .module('horizon.framework', [
'horizon.framework.util', 'horizon.framework.conf',
'horizon.framework.widgets' 'horizon.framework.util',
]) 'horizon.framework.widgets'
.constant('horizon.framework.basePath', '/static/framework/') ])
.constant('horizon.framework.basePath', '/static/framework/')
.config([
'$interpolateProvider',
'$httpProvider',
function ($interpolateProvider, $httpProvider) {
// Replacing the default angular symbol
// allow us to mix angular with django templates
$interpolateProvider.startSymbol('{$');
$interpolateProvider.endSymbol('$}');
.config(['$interpolateProvider', '$httpProvider', // Http global settings for ease of use
function ($interpolateProvider, $httpProvider) { $httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';
$httpProvider.defaults.xsrfCookieName = 'csrftoken';
$httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
$httpProvider.defaults.headers.common['Content-Type'] = 'application/json;charset=utf-8';
// Replacing the default angular symbol // Global http error handler
// allow us to mix angular with django templates // if user is not authorized, log user out
$interpolateProvider.startSymbol('{$'); // this can happen when session expires
$interpolateProvider.endSymbol('$}'); $httpProvider.interceptors.push(function ($q) {
return {
// Http global settings for ease of use responseError: function (error) {
$httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken'; if (error.status === 401) {
$httpProvider.defaults.xsrfCookieName = 'csrftoken'; window.location.replace('/auth/logout');
$httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; }
$httpProvider.defaults.headers.common['Content-Type'] = 'application/json;charset=utf-8'; return $q.reject(error);
// Global http error handler
// if user is not authorized, log user out
// this can happen when session expires
$httpProvider.interceptors.push(function ($q) {
return {
responseError: function (error) {
if (error.status === 401) {
window.location.replace('/auth/logout');
} }
return $q.reject(error); };
} });
}; }
}); ]);
}]);
})(); })();

View File

@ -1,4 +1,4 @@
(function() { (function () {
'use strict'; 'use strict';
/** /**
@ -41,12 +41,12 @@
* </tr> * </tr>
* ``` * ```
*/ */
.directive('bindScope', function() { .directive('bindScope', function () {
return { return {
restrict: 'A', restrict: 'A',
link: function(scope, element, attrs, ctrl, transclude) { link: function (scope, element, attrs, ctrl, transclude) {
if (transclude) { if (transclude) {
transclude(scope, function(clone) { transclude(scope, function (clone) {
var detailElt = element.find('[bind-scope-target]'); var detailElt = element.find('[bind-scope-target]');
if (detailElt.length) { if (detailElt.length) {
detailElt.append(clone); detailElt.append(clone);
@ -56,5 +56,4 @@
} }
}; };
}); });
})();
})();

View File

@ -1,22 +1,21 @@
(function() { (function () {
'use strict'; 'use strict';
describe('horizon.framework.util.bind-scope module', function() { describe('horizon.framework.util.bind-scope module', function () {
it('should have been defined', function() { it('should have been defined', function () {
expect(angular.module('horizon.framework.util.bind-scope')).toBeDefined(); expect(angular.module('horizon.framework.util.bind-scope')).toBeDefined();
}); });
}); });
describe('bind-scope directive', function() { describe('bind-scope directive', function () {
var $scope, $element; var $scope, $element;
beforeEach(module('horizon.framework')); beforeEach(module('horizon.framework'));
beforeEach(module('horizon.framework.widgets')); beforeEach(module('horizon.framework.widgets'));
beforeEach(module('horizon.framework.util.bind-scope')); beforeEach(module('horizon.framework.util.bind-scope'));
beforeEach(module('horizon.framework.util.bind-scope', function($compileProvider) { beforeEach(module('horizon.framework.util.bind-scope', function ($compileProvider) {
$compileProvider.directive('testBindScope', function() { $compileProvider.directive('testBindScope', function () {
return { return {
restrict: 'E', restrict: 'E',
scope: { scope: {
@ -30,7 +29,7 @@
}); });
})); }));
beforeEach(inject(function($injector) { beforeEach(inject(function ($injector) {
var $compile = $injector.get('$compile'); var $compile = $injector.get('$compile');
$scope = $injector.get('$rootScope').$new(); $scope = $injector.get('$rootScope').$new();
@ -48,17 +47,15 @@
$scope.$digest(); $scope.$digest();
})); }));
it('should have 3 list items', function() { it('should have 3 list items', function () {
expect($element.find('li').length).toBe(3); expect($element.find('li').length).toBe(3);
}); });
it('should have 3 list items with values "cat", "dog" and "fish"', function() { it('should have 3 list items with values "cat", "dog" and "fish"', function () {
var listItems = $element.find('li'); var listItems = $element.find('li');
expect(listItems[0].textContent.trim()).toBe('cat'); expect(listItems[0].textContent.trim()).toBe('cat');
expect(listItems[1].textContent.trim()).toBe('dog'); expect(listItems[1].textContent.trim()).toBe('dog');
expect(listItems[2].textContent.trim()).toBe('fish'); expect(listItems[2].textContent.trim()).toBe('fish');
}); });
}); });
})();
})();

View File

@ -25,128 +25,128 @@
*/ */
angular.module('horizon.framework.util.filters', ['horizon.framework.util.i18n']) angular.module('horizon.framework.util.filters', ['horizon.framework.util.i18n'])
/** /**
* @ngdoc filter * @ngdoc filter
* @name yesno * @name yesno
* @description * @description
* Evaluates given input for standard truthiness and returns translation * Evaluates given input for standard truthiness and returns translation
* of 'Yes' and 'No' for true/false respectively. * of 'Yes' and 'No' for true/false respectively.
*/ */
.filter('yesno', ['horizon.framework.util.i18n.gettext', function(gettext) { .filter('yesno', ['horizon.framework.util.i18n.gettext', function (gettext) {
return function(input) { return function (input) {
return (input ? gettext("Yes") : gettext("No")); return (input ? gettext("Yes") : gettext("No"));
}; };
}]) }])
/** /**
* @ngdoc filter * @ngdoc filter
* @name gb * @name gb
* @description * @description
* Expects numeric value and suffixes translated 'GB' with spacing. * Expects numeric value and suffixes translated 'GB' with spacing.
* Returns empty string if input is not a number or is null. * Returns empty string if input is not a number or is null.
*/ */
.filter('gb', function() { .filter('gb', function () {
return function(input) { return function (input) {
if (isNaN(input) || null === input) { if (isNaN(input) || null === input) {
return ''; return '';
} else { } else {
return interpolate(gettext("%s GB"), [input.toString()]); return interpolate(gettext("%s GB"), [input.toString()]);
} }
}; };
}) })
/** /**
* @ngdoc filter * @ngdoc filter
* @name mb * @name mb
* @description * @description
* Expects numeric value and suffixes translated 'MB' with spacing. * Expects numeric value and suffixes translated 'MB' with spacing.
* Returns empty string if input is not a number or is null. * Returns empty string if input is not a number or is null.
*/ */
.filter('mb', function() { .filter('mb', function () {
return function(input) { return function (input) {
if (isNaN(input) || null === input) { if (isNaN(input) || null === input) {
return ''; return '';
} else { } else {
return interpolate(gettext("%s MB"), [input.toString()]); return interpolate(gettext("%s MB"), [input.toString()]);
} }
}; };
}) })
/** /**
* @ngdoc filter * @ngdoc filter
* @name title * @name title
* @description * @description
* Capitalizes leading characters of individual words. * Capitalizes leading characters of individual words.
*/ */
.filter('title', function() { .filter('title', function () {
return function(input) { return function (input) {
if (typeof input !== 'string') { if (typeof input !== 'string') {
return input; return input;
} }
return input.replace(/(?:^|\s)\S/g, function(a) { return input.replace(/(?:^|\s)\S/g, function (a) {
return a.toUpperCase(); return a.toUpperCase();
}); });
}; };
}) })
/** /**
* @ngdoc filter * @ngdoc filter
* @name noUnderscore * @name noUnderscore
* @description * @description
* Replaces all underscores with spaces. * Replaces all underscores with spaces.
*/ */
.filter('noUnderscore', function() { .filter('noUnderscore', function () {
return function(input) { return function (input) {
if (typeof input !== 'string') { if (typeof input !== 'string') {
return input; return input;
} }
return input.replace(/_/g, ' '); return input.replace(/_/g, ' ');
}; };
}) })
/** /**
* @ngdoc filter * @ngdoc filter
* @name decode * @name decode
* @description * @description
* Returns values based on key and given mapping. If key doesn't exist * Returns values based on key and given mapping. If key doesn't exist
* in given mapping, return key. This is useful when translations for * in given mapping, return key. This is useful when translations for
* codes are present. * codes are present.
*/ */
.filter('decode', function() { .filter('decode', function () {
return function(input, mapping) { return function (input, mapping) {
var val = mapping[input]; var val = mapping[input];
return angular.isDefined(val) ? val : input; return angular.isDefined(val) ? val : input;
}; };
}) })
/** /**
* @ngdoc filter * @ngdoc filter
* @name bytes * @name bytes
* @description * @description
* Returns a human-readable approximation of the input of bytes, * Returns a human-readable approximation of the input of bytes,
* converted to a useful unit of measure. Uses 1024-based notation. * converted to a useful unit of measure. Uses 1024-based notation.
*/ */
.filter('bytes', function() { .filter('bytes', function () {
return function(input) { return function (input) {
var kb = 1024; var kb = 1024;
var mb = kb*1024; var mb = kb * 1024;
var gb = mb*1024; var gb = mb * 1024;
var tb = gb*1024; var tb = gb * 1024;
if (isNaN(input) || null === input || input < 0) { if (isNaN(input) || null === input || input < 0) {
return ''; return '';
} else if (input >= tb) { } else if (input >= tb) {
return interpolate(gettext("%s TB"), [Number(input/tb).toFixed(2)]); return interpolate(gettext("%s TB"), [Number(input / tb).toFixed(2)]);
} else if (input >= gb) { } else if (input >= gb) {
return interpolate(gettext("%s GB"), [Number(input/gb).toFixed(2)]); return interpolate(gettext("%s GB"), [Number(input / gb).toFixed(2)]);
} else if (input >= mb) { } else if (input >= mb) {
return interpolate(gettext("%s MB"), [Number(input/mb).toFixed(2)]); return interpolate(gettext("%s MB"), [Number(input / mb).toFixed(2)]);
} else if (input >= kb) { } else if (input >= kb) {
return interpolate(gettext("%s KB"), [Number(input/kb).toFixed(2)]); return interpolate(gettext("%s KB"), [Number(input / kb).toFixed(2)]);
} else { } else {
return interpolate(gettext("%s bytes"), [Math.floor(input)]); return interpolate(gettext("%s bytes"), [Math.floor(input)]);
} }
}; };
}) })
/** /**
* @ngdoc filter * @ngdoc filter
@ -155,11 +155,11 @@
* Displays translated count in table footer. * Displays translated count in table footer.
* Takes only finite numbers. * Takes only finite numbers.
*/ */
.filter('itemCount', function() { .filter('itemCount', function () {
return function(input) { return function (input) {
var isNumeric = (input !== null && isFinite(input)); var isNumeric = (input !== null && isFinite(input));
var number = isNumeric ? Math.round(input): 0; var number = isNumeric ? Math.round(input) : 0;
var count = (number > 0) ? number: 0; var count = (number > 0) ? number : 0;
var format = ngettext('Displaying %s item', 'Displaying %s items', count); var format = ngettext('Displaying %s item', 'Displaying %s items', count);
return interpolate(format, [count]); return interpolate(format, [count]);
}; };
@ -171,11 +171,10 @@
* @description * @description
* Returns translated text. * Returns translated text.
*/ */
.filter('trans', ['horizon.framework.util.i18n.gettext', function(gettextFunc) { .filter('trans', ['horizon.framework.util.i18n.gettext', function (gettextFunc) {
return function(input) { return function (input) {
// NOTE: uses 'gettextFunc' to avoid message collection. // NOTE: uses 'gettextFunc' to avoid message collection.
return gettextFunc(input); return gettextFunc(input);
}; };
}]); }]);
}()); }());

View File

@ -1,220 +1,211 @@
describe('horizon.framework.util.filters', function () { (function () {
'use strict'; 'use strict';
beforeEach(module('horizon.framework.util.i18n')); describe('horizon.framework.util.filters', function () {
beforeEach(module('horizon.framework.util.filters')); beforeEach(module('horizon.framework.util.i18n'));
beforeEach(module('horizon.framework.util.filters'));
describe('yesno', function () { describe('yesno', function () {
var yesnoFilter; var yesnoFilter;
beforeEach(inject(function (_yesnoFilter_) { beforeEach(inject(function (_yesnoFilter_) {
yesnoFilter = _yesnoFilter_; yesnoFilter = _yesnoFilter_;
})); }));
it('returns Yes for true', function () { it('returns Yes for true', function () {
expect(yesnoFilter(true)).toBe('Yes'); expect(yesnoFilter(true)).toBe('Yes');
});
it('returns No for false', function () {
expect(yesnoFilter(false)).toBe('No');
});
it('returns No for null', function () {
expect(yesnoFilter(null)).toBe('No');
});
it('returns No for undefined', function () {
expect(yesnoFilter(undefined)).toBe('No');
});
it('returns Yes for other truthy values', function () {
expect(yesnoFilter(7)).toBe('Yes');
expect(yesnoFilter('C')).toBe('Yes');
expect(yesnoFilter('This will be truthy')).toBe('Yes');
});
it('returns No for other falsy values', function () {
expect(yesnoFilter(0)).toBe('No');
expect(yesnoFilter('')).toBe('No');
});
}); });
it('returns No for false', function () { describe('gb', function () {
expect(yesnoFilter(false)).toBe('No'); var gbFilter;
beforeEach(inject(function (_gbFilter_) {
gbFilter = _gbFilter_;
}));
it('returns given numeric value properly', function () {
expect(gbFilter(12)).toBe('12 GB');
expect(gbFilter(-12)).toBe('-12 GB');
expect(gbFilter(12.12)).toBe('12.12 GB');
});
it('returns empty string for non-numeric', function () {
expect(gbFilter('humbug')).toBe('');
});
it('returns empty string for null', function () {
expect(gbFilter(null)).toBe('');
});
}); });
it('returns No for null', function () { describe('mb', function () {
expect(yesnoFilter(null)).toBe('No'); var mbFilter;
beforeEach(inject(function (_mbFilter_) {
mbFilter = _mbFilter_;
}));
it('returns given numeric value properly', function () {
expect(mbFilter(12)).toBe('12 MB');
expect(mbFilter(-12)).toBe('-12 MB');
expect(mbFilter(12.12)).toBe('12.12 MB');
});
it('returns empty string for non-numeric', function () {
expect(mbFilter('humbug')).toBe('');
});
it('returns empty string for null', function () {
expect(mbFilter(null)).toBe('');
});
}); });
it('returns No for undefined', function () { describe('title', function () {
expect(yesnoFilter(undefined)).toBe('No'); var titleFilter;
beforeEach(inject(function (_titleFilter_) {
titleFilter = _titleFilter_;
}));
it('capitalizes as expected', function () {
expect(titleFilter('title')).toBe('Title');
expect(titleFilter('we have several words')).toBe('We Have Several Words');
});
it('handles non-strings correctly', function () {
expect(titleFilter(42)).toBe(42);
expect(titleFilter(null)).toBe(null);
expect(titleFilter(undefined)).toBe(undefined);
});
it('does not mess up properly capitalized strings', function () {
expect(titleFilter('I Love OpenStack Horizon!')).toBe('I Love OpenStack Horizon!');
});
it('handles strings beginning with numbers', function () {
expect(titleFilter('3abc')).toBe('3abc');
});
}); });
it('returns Yes for other truthy values', function () { describe('noUnderscore', function () {
expect(yesnoFilter(7)).toBe('Yes'); var noUnderscoreFilter;
expect(yesnoFilter('C')).toBe('Yes'); beforeEach(inject(function (_noUnderscoreFilter_) {
expect(yesnoFilter('This will be truthy')).toBe('Yes'); noUnderscoreFilter = _noUnderscoreFilter_;
}));
it('replaces all underscores with spaces', function () {
expect(noUnderscoreFilter('_this_is___a_lot____of_underscores__'))
.toBe(' this is a lot of underscores ');
});
it('returns non-string input', function () {
expect(noUnderscoreFilter(null)).toBe(null);
expect(noUnderscoreFilter(false)).toBe(false);
expect(noUnderscoreFilter(true)).toBe(true);
expect(noUnderscoreFilter('')).toBe('');
expect(noUnderscoreFilter(21)).toBe(21);
});
}); });
it('returns No for other falsy values', function () { describe("decode", function () {
expect(yesnoFilter(0)).toBe('No'); var decodeFilter;
expect(yesnoFilter('')).toBe('No'); beforeEach(inject(function (_decodeFilter_) {
decodeFilter = _decodeFilter_;
}));
it("Returns value when key is present", function () {
expect(decodeFilter('PRESENT', {'PRESENT': 'Here'})).toBe('Here');
});
it("Returns value when key is present and value is falsy", function () {
expect(decodeFilter('PRESENT', {'PRESENT': false})).toBe(false);
});
it("Returns input when key is not present", function () {
expect(decodeFilter('NOT_PRESENT', {'PRESENT': 'Here'})).toBe('NOT_PRESENT');
});
}); });
describe('bytes', function () {
var bytesFilter;
beforeEach(inject(function (_bytesFilter_) {
bytesFilter = _bytesFilter_;
}));
it('returns TB values', function () {
expect(bytesFilter(1099511627776)).toBe('1.00 TB');
});
it('returns GB values', function () {
expect(bytesFilter(1073741824)).toBe('1.00 GB');
});
it('returns MB values', function () {
expect(bytesFilter(1048576)).toBe('1.00 MB');
});
it('returns KB values', function () {
expect(bytesFilter(1024)).toBe('1.00 KB');
});
it('returns byte values', function () {
expect(bytesFilter(0)).toBe('0 bytes');
expect(bytesFilter(1)).toBe('1 bytes');
expect(bytesFilter(1023)).toBe('1023 bytes');
});
it('handles non-numbers correctly', function () {
expect(bytesFilter('Yo!')).toBe('');
expect(bytesFilter(null)).toBe('');
});
});
describe('itemCount', function () {
it('should return translated text with item count',
inject(function (itemCountFilter) {
expect(itemCountFilter(null)).toBe('Displaying 0 items');
expect(itemCountFilter(undefined)).toBe('Displaying 0 items');
expect(itemCountFilter(true)).toBe('Displaying 1 item');
expect(itemCountFilter(false)).toBe('Displaying 0 items');
expect(itemCountFilter('a')).toBe('Displaying 0 items');
expect(itemCountFilter('0')).toBe('Displaying 0 items');
expect(itemCountFilter('1')).toBe('Displaying 1 item');
expect(itemCountFilter('1e1')).toBe('Displaying 10 items');
expect(itemCountFilter('1b1')).toBe('Displaying 0 items');
expect(itemCountFilter(0)).toBe('Displaying 0 items');
expect(itemCountFilter(1)).toBe('Displaying 1 item');
expect(itemCountFilter(1.2)).toBe('Displaying 1 item');
expect(itemCountFilter(1.6)).toBe('Displaying 2 items');
expect(itemCountFilter(-1)).toBe('Displaying 0 items');
expect(itemCountFilter(-1.2)).toBe('Displaying 0 items');
})
);
});
describe('trans', function () {
it('should return translated text', inject(function (transFilter) {
expect(transFilter("Howdy")).toBe("Howdy");
}));
});
}); });
})();
describe('gb', function () {
var gbFilter;
beforeEach(inject(function (_gbFilter_) {
gbFilter = _gbFilter_;
}));
it('returns given numeric value properly', function () {
expect(gbFilter(12)).toBe('12 GB');
expect(gbFilter(-12)).toBe('-12 GB');
expect(gbFilter(12.12)).toBe('12.12 GB');
});
it('returns empty string for non-numeric', function () {
expect(gbFilter('humbug')).toBe('');
});
it('returns empty string for null', function () {
expect(gbFilter(null)).toBe('');
});
});
describe('mb', function () {
var mbFilter;
beforeEach(inject(function (_mbFilter_) {
mbFilter = _mbFilter_;
}));
it('returns given numeric value properly', function () {
expect(mbFilter(12)).toBe('12 MB');
expect(mbFilter(-12)).toBe('-12 MB');
expect(mbFilter(12.12)).toBe('12.12 MB');
});
it('returns empty string for non-numeric', function () {
expect(mbFilter('humbug')).toBe('');
});
it('returns empty string for null', function () {
expect(mbFilter(null)).toBe('');
});
});
describe('title', function () {
var titleFilter;
beforeEach(inject(function (_titleFilter_) {
titleFilter = _titleFilter_;
}));
it('capitalizes as expected', function () {
expect(titleFilter('title')).toBe('Title');
expect(titleFilter('we have several words')).toBe('We Have Several Words');
});
it('handles non-strings correctly', function () {
expect(titleFilter(42)).toBe(42);
expect(titleFilter(null)).toBe(null);
expect(titleFilter(undefined)).toBe(undefined);
});
it('does not mess up properly capitalized strings', function () {
expect(titleFilter('I Love OpenStack Horizon!')).toBe('I Love OpenStack Horizon!');
});
it('handles strings beginning with numbers', function () {
expect(titleFilter('3abc')).toBe('3abc');
});
});
describe('noUnderscore', function () {
var noUnderscoreFilter;
beforeEach(inject(function (_noUnderscoreFilter_) {
noUnderscoreFilter = _noUnderscoreFilter_;
}));
it('replaces all underscores with spaces', function () {
expect(noUnderscoreFilter('_this_is___a_lot____of_underscores__'))
.toBe(' this is a lot of underscores ');
});
it('returns non-string input', function () {
expect(noUnderscoreFilter(null)).toBe(null);
expect(noUnderscoreFilter(false)).toBe(false);
expect(noUnderscoreFilter(true)).toBe(true);
expect(noUnderscoreFilter('')).toBe('');
expect(noUnderscoreFilter(21)).toBe(21);
});
});
describe("decode", function () {
var decodeFilter;
beforeEach(inject(function (_decodeFilter_) {
decodeFilter = _decodeFilter_;
}));
it("Returns value when key is present", function () {
expect(decodeFilter('PRESENT', {'PRESENT': 'Here'})).toBe('Here');
});
it("Returns value when key is present and value is falsy", function () {
expect(decodeFilter('PRESENT', {'PRESENT': false})).toBe(false);
});
it("Returns input when key is not present", function () {
expect(decodeFilter('NOT_PRESENT', {'PRESENT': 'Here'})).toBe('NOT_PRESENT');
});
});
describe('bytes', function () {
var bytesFilter;
beforeEach(inject(function (_bytesFilter_) {
bytesFilter = _bytesFilter_;
}));
it('returns TB values', function () {
expect(bytesFilter(1099511627776)).toBe('1.00 TB');
});
it('returns GB values', function () {
expect(bytesFilter(1073741824)).toBe('1.00 GB');
});
it('returns MB values', function () {
expect(bytesFilter(1048576)).toBe('1.00 MB');
});
it('returns KB values', function () {
expect(bytesFilter(1024)).toBe('1.00 KB');
});
it('returns byte values', function () {
expect(bytesFilter(0)).toBe('0 bytes');
expect(bytesFilter(1)).toBe('1 bytes');
expect(bytesFilter(1023)).toBe('1023 bytes');
});
it('handles non-numbers correctly', function () {
expect(bytesFilter('Yo!')).toBe('');
expect(bytesFilter(null)).toBe('');
});
});
describe('itemCount', function () {
it('should return translated text with item count',
inject(function (itemCountFilter) {
expect(itemCountFilter(null)).toBe('Displaying 0 items');
expect(itemCountFilter(undefined)).toBe('Displaying 0 items');
expect(itemCountFilter(true)).toBe('Displaying 1 item');
expect(itemCountFilter(false)).toBe('Displaying 0 items');
expect(itemCountFilter('a')).toBe('Displaying 0 items');
expect(itemCountFilter('0')).toBe('Displaying 0 items');
expect(itemCountFilter('1')).toBe('Displaying 1 item');
expect(itemCountFilter('1e1')).toBe('Displaying 10 items');
expect(itemCountFilter('1b1')).toBe('Displaying 0 items');
expect(itemCountFilter(0)).toBe('Displaying 0 items');
expect(itemCountFilter(1)).toBe('Displaying 1 item');
expect(itemCountFilter(1.2)).toBe('Displaying 1 item');
expect(itemCountFilter(1.6)).toBe('Displaying 2 items');
expect(itemCountFilter(-1)).toBe('Displaying 0 items');
expect(itemCountFilter(-1.2)).toBe('Displaying 0 items');
})
);
});
describe('trans', function() {
it('should return translated text', inject(function(transFilter) {
expect(transFilter("Howdy")).toBe("Howdy");
}));
});
});

View File

@ -13,43 +13,37 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
(function () {
(function() {
'use strict'; 'use strict';
angular.module("horizon.framework.util.i18n", []) angular.module('horizon.framework.util.i18n', [])
/*
* @name horizon.framework.util.i18n.gettext
* @description
* Provides a wrapper for translation, using the global 'gettext'
* function if it is present. Provides a method that
* simply returns the input if the expected global 'gettext' is
* not provided.
*
* Ideally once gettext is no longer needed on a global scope,
* the global ref can be deleted here. For now that is not possible.
* Also, if future alternate means were provided, we could put that
* logic here.
*
* This could also be done in the context of the filter, but
* the approach taken here was to separate business logic
* (translation) from filters, which are arguably more
* presentation-oriented.
*/
.factory("horizon.framework.util.i18n.gettext", ['$window', function($window) {
// If no global function, revert to just returning given text.
var gettextFunc = $window.gettext || function(x) { return x; };
// Eventually, could delete the window gettext references here,
// or provide an appropriate method.
return function() {
return gettextFunc.apply(this, arguments);
};
}]);
/*
* @name horizon.framework.util.i18n.gettext
* @description
* Provides a wrapper for translation, using the global 'gettext'
* function if it is present. Provides a method that
* simply returns the input if the expected global 'gettext' is
* not provided.
*
* Ideally once gettext is no longer needed on a global scope,
* the global ref can be deleted here. For now that is not possible.
* Also, if future alternate means were provided, we could put that
* logic here.
*
* This could also be done in the context of the filter, but
* the approach taken here was to separate business logic
* (translation) from filters, which are arguably more
* presentation-oriented.
*/
.factory('horizon.framework.util.i18n.gettext', ['$window', function ($window) {
// If no global function, revert to just returning given text.
var gettextFunc = $window.gettext || function (x) { return x; };
// Eventually, could delete the window gettext references here,
// or provide an appropriate method.
return function () {
return gettextFunc.apply(this, arguments);
};
}]);
})(); })();

View File

@ -13,52 +13,46 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
(function () {
(function() {
'use strict'; 'use strict';
describe('horizon.framework.util.i18n', function() { describe('horizon.framework.util.i18n', function () {
beforeEach(module('horizon.framework.util.i18n')); beforeEach(module('horizon.framework.util.i18n'));
describe('gettext', function() { describe('gettext', function () {
var factory; var factory;
describe('Normal operation', function() { describe('Normal operation', function () {
beforeEach(inject(function ($injector) {
beforeEach(inject(function($injector) {
factory = $injector.get("horizon.framework.util.i18n.gettext"); factory = $injector.get("horizon.framework.util.i18n.gettext");
})); }));
it('defines the factory', function() { it('defines the factory', function () {
expect(factory).toBeDefined(); expect(factory).toBeDefined();
}); });
it('function returns what is given', function() { it('function returns what is given', function () {
expect(factory("Hello")).toBe('Hello'); expect(factory("Hello")).toBe('Hello');
}); });
}); });
describe("injected window.gettext", function() { describe("injected window.gettext", function () {
beforeEach(module(function ($provide) {
beforeEach(module(function($provide) { var $window = { gettext: function (x) { return x.replace(/good/, 'bad'); } };
var $window = {gettext: function(x) { return x.replace(/good/, 'bad'); }};
$provide.value('$window', $window); $provide.value('$window', $window);
})); }));
// Get the factory by name. // Get the factory by name.
beforeEach(inject(function($injector) { beforeEach(inject(function ($injector) {
factory = $injector.get("horizon.framework.util.i18n.gettext"); factory = $injector.get("horizon.framework.util.i18n.gettext");
})); }));
it('uses the window gettext when available', function() { it('uses the window gettext when available', function () {
// we can't spy on the window gettext due to (appropriate) // we can't spy on the window gettext due to (appropriate)
// indirection. But we can make sure it was called. // indirection. But we can make sure it was called.
expect(factory("good cop")).toBe("bad cop"); expect(factory("good cop")).toBe("bad cop");
}); });
}); });
}); });
}); });
})(); })();

View File

@ -11,5 +11,4 @@
'horizon.framework.util.validators' 'horizon.framework.util.validators'
]) ])
.constant('horizon.framework.util.basePath', '/static/framework/util/'); .constant('horizon.framework.util.basePath', '/static/framework/util/');
})(); })();

View File

@ -15,6 +15,7 @@
* |---------------------------------------------------------------------------------| * |---------------------------------------------------------------------------------|
* | {@link horizon.framework.util.validators.directive:validateNumberMax `validateNumberMax`} | * | {@link horizon.framework.util.validators.directive:validateNumberMax `validateNumberMax`} |
* | {@link horizon.framework.util.validators.directive:validateNumberMin `validateNumberMin`} | * | {@link horizon.framework.util.validators.directive:validateNumberMin `validateNumberMin`} |
* | {@link horizon.framework.util.validators.directive:notBlank `notBlank`} |
* | {@link horizon.framework.util.validators.directive:hzPasswordMatch `hzPasswordMatch`} | * | {@link horizon.framework.util.validators.directive:hzPasswordMatch `hzPasswordMatch`} |
* *
*/ */
@ -47,38 +48,40 @@
* validate-number-max="{$ maxNumber $}"> * validate-number-max="{$ maxNumber $}">
* ``` * ```
*/ */
.directive('validateNumberMax', [function () { .directive('validateNumberMax', [function () {
return { return {
require: 'ngModel', require: 'ngModel',
restrict: 'A', restrict: 'A',
link: function (scope, element, attrs, ctrl) { link: function (scope, element, attrs, ctrl) {
if (!ctrl) { if (!ctrl) {
return; return;
}
var maxValidator = function (value) {
var max = scope.$eval(attrs.validateNumberMax);
if (angular.isDefined(max) && !ctrl.$isEmpty(value) && value > max) {
ctrl.$setValidity('validateNumberMax', false);
} else {
ctrl.$setValidity('validateNumberMax', true);
} }
var maxValidator = function (value) { // Return the value rather than undefined if invalid
var max = scope.$eval(attrs.validateNumberMax); return value;
if (angular.isDefined(max) && !ctrl.$isEmpty(value) && value > max) { };
ctrl.$setValidity('validateNumberMax', false);
} else {
ctrl.$setValidity('validateNumberMax', true);
}
// Return the value rather than undefined if invalid /**
return value; * Re-validate if value is changed through the UI
}; * or model (programmatically)
*/
ctrl.$parsers.push(maxValidator);
ctrl.$formatters.push(maxValidator);
// Re-validate if value is changed through the UI attrs.$observe('validateNumberMax', function () {
// or model (programmatically) maxValidator(ctrl.$modelValue);
ctrl.$parsers.push(maxValidator); });
ctrl.$formatters.push(maxValidator); }
};
attrs.$observe('validateNumberMax', function () { }])
maxValidator(ctrl.$modelValue);
});
}
};
}])
/** /**
* @ngdoc directive * @ngdoc directive
@ -107,107 +110,115 @@
* validate-number-min="{$ minNumber $}"> * validate-number-min="{$ minNumber $}">
* ``` * ```
*/ */
.directive('validateNumberMin', [function () { .directive('validateNumberMin', [function () {
return { return {
require: 'ngModel', require: 'ngModel',
restrict: 'A', restrict: 'A',
link: function (scope, element, attrs, ctrl) { link: function (scope, element, attrs, ctrl) {
if (!ctrl) { if (!ctrl) {
return; return;
}
var minValidator = function (value) {
var min = scope.$eval(attrs.validateNumberMin);
if (angular.isDefined(min) && !ctrl.$isEmpty(value) && value < min) {
ctrl.$setValidity('validateNumberMin', false);
} else {
ctrl.$setValidity('validateNumberMin', true);
} }
var minValidator = function (value) { // Return the value rather than undefined if invalid
var min = scope.$eval(attrs.validateNumberMin); return value;
if (angular.isDefined(min) && !ctrl.$isEmpty(value) && value < min) { };
ctrl.$setValidity('validateNumberMin', false);
} else {
ctrl.$setValidity('validateNumberMin', true);
}
// Return the value rather than undefined if invalid /**
return value; * Re-validate if value is changed through the UI
}; * or model (programmatically)
*/
ctrl.$parsers.push(minValidator);
ctrl.$formatters.push(minValidator);
// Re-validate if value is changed through the UI attrs.$observe('validateNumberMin', function () {
// or model (programmatically) minValidator(ctrl.$modelValue);
ctrl.$parsers.push(minValidator); });
ctrl.$formatters.push(minValidator); }
};
}])
attrs.$observe('validateNumberMin', function () { /**
minValidator(ctrl.$modelValue); * @ngdoc directive
}); * @name notBlank
} * @element ng-model
}; * @description Ensure that the value is not blank
}]) */
.directive('notBlank', function () {
.directive('notBlank', function () { return {
return { require: 'ngModel',
require: 'ngModel', link: function (scope, elm, attrs, ctrl) {
link: function (scope, elm, attrs, ctrl) { ctrl.$parsers.unshift(function (viewValue) {
ctrl.$parsers.unshift(function (viewValue) { if (viewValue.length) {
if (viewValue.length) { // it is valid
// it is valid ctrl.$setValidity('notBlank', true);
ctrl.$setValidity('notBlank', true); return viewValue;
return viewValue;
}
// it is invalid, return undefined (no model update)
ctrl.$setValidity('notBlank', false);
return undefined;
});
}
};
})
/**
* @ngdoc directive
* @name hzPasswordMatch
* @element ng-model
*
* @description
* A directive to ensure that password matches.
* Changing the password or confirmation password triggers a validation check.
* However, only the confirmation password will show an error if match is false.
* The goal is to check that confirmation password matches the password,
* not whether the password matches the confirmation password.
* The behavior here is NOT bi-directional.
*
* @restrict A
*
* @scope
* hzPasswordMatch - form model to validate against
*
* @example:
* <form name="form">
* <input type='password' id="psw" ng-model="user.psw" name="psw">
* <input type='password' ng-model="user.cnf" hz-password-match="form.psw">
* </form>
*
* Note that id and name are required for the password input.
* This directive uses the form model and id for validation check.
*/
.directive('hzPasswordMatch', function(){
return {
restrict: 'A',
require: 'ngModel',
scope: { pw: '=hzPasswordMatch' },
link: function(scope, element, attr, ctrl){
// helper function to check that password matches
function passwordCheck(){
scope.$apply(function(){
var match = (ctrl.$modelValue === scope.pw.$modelValue);
ctrl.$setValidity('match', match);
});
} }
// it is invalid, return undefined (no model update)
ctrl.$setValidity('notBlank', false);
return undefined;
});
}
};
})
// this ensures that typing in either input /**
// will trigger the password match * @ngdoc directive
var pwElement = $('#'+scope.pw.$name); * @name hzPasswordMatch
pwElement.on('keyup change', passwordCheck); * @element ng-model
element.on('keyup change', passwordCheck); *
* @description
* A directive to ensure that password matches.
* Changing the password or confirmation password triggers a validation check.
* However, only the confirmation password will show an error if match is false.
* The goal is to check that confirmation password matches the password,
* not whether the password matches the confirmation password.
* The behavior here is NOT bi-directional.
*
* @restrict A
*
* @scope
* hzPasswordMatch - form model to validate against
*
* @example:
* <form name="form">
* <input type='password' id="psw" ng-model="user.psw" name="psw">
* <input type='password' ng-model="user.cnf" hz-password-match="form.psw">
* </form>
*
* Note that id and name are required for the password input.
* This directive uses the form model and id for validation check.
*/
.directive('hzPasswordMatch', function () {
return {
restrict: 'A',
require: 'ngModel',
scope: { pw: '=hzPasswordMatch' },
link: function (scope, element, attr, ctrl) {
} // end of link // helper function to check that password matches
}; // end of return function passwordCheck() {
}); // end of directive scope.$apply(function () {
var match = (ctrl.$modelValue === scope.pw.$modelValue);
ctrl.$setValidity('match', match);
});
}
}()); /**
* this ensures that typing in either input
* will trigger the password match
*/
var pwElement = $('#' + scope.pw.$name);
pwElement.on('keyup change', passwordCheck);
element.on('keyup change', passwordCheck);
} // end of link
}; // end of return
}); // end of directive
})();

View File

@ -1,22 +1,20 @@
(function() { (function () {
'use strict'; 'use strict';
describe('horizon.framework.util.validators module', function() { describe('horizon.framework.util.validators module', function () {
it('should have been defined', function() { it('should have been defined', function () {
expect(angular.module('horizon.framework.util.validators')).toBeDefined(); expect(angular.module('horizon.framework.util.validators')).toBeDefined();
}); });
}); });
describe('validators directive', function() { describe('validators directive', function () {
beforeEach(module('horizon.framework.widgets')); beforeEach(module('horizon.framework.widgets'));
beforeEach(module('horizon.framework.util.validators')); beforeEach(module('horizon.framework.util.validators'));
describe('validateNumberMax directive', function() { describe('validateNumberMax directive', function () {
var $scope, $form; var $scope, $form;
beforeEach(inject(function($injector) { beforeEach(inject(function ($injector) {
var $compile = $injector.get('$compile'); var $compile = $injector.get('$compile');
$scope = $injector.get('$rootScope').$new(); $scope = $injector.get('$rootScope').$new();
@ -33,32 +31,30 @@
$scope.$digest(); $scope.$digest();
})); }));
it('should pass validation initially when count is 0 and max is 1', function() { it('should pass validation initially when count is 0 and max is 1', function () {
expect($form.count.$valid).toBe(true); expect($form.count.$valid).toBe(true);
expect($form.$valid).toBe(true); expect($form.$valid).toBe(true);
}); });
it('should not pass validation if count increased to 2 and max is 1', function() { it('should not pass validation if count increased to 2 and max is 1', function () {
$form.count.$setViewValue(2); $form.count.$setViewValue(2);
$scope.$digest(); $scope.$digest();
expect($form.count.$valid).toBe(false); expect($form.count.$valid).toBe(false);
expect($form.$valid).toBe(false); expect($form.$valid).toBe(false);
}); });
it('should pass validation if count is empty', function() { it('should pass validation if count is empty', function () {
$form.count.$setViewValue(''); $form.count.$setViewValue('');
$scope.$digest(); $scope.$digest();
expect($form.count.$valid).toBe(true); expect($form.count.$valid).toBe(true);
expect($form.$valid).toBe(true); expect($form.$valid).toBe(true);
}); });
}); });
describe('validateNumberMin directive', function() { describe('validateNumberMin directive', function () {
var $scope, $form; var $scope, $form;
beforeEach(inject(function($injector) { beforeEach(inject(function ($injector) {
var $compile = $injector.get('$compile'); var $compile = $injector.get('$compile');
$scope = $injector.get('$rootScope').$new(); $scope = $injector.get('$rootScope').$new();
@ -75,12 +71,12 @@
$scope.$digest(); $scope.$digest();
})); }));
it('should not pass validation initially when count is 0 and min is 1', function() { it('should not pass validation initially when count is 0 and min is 1', function () {
expect($form.count.$valid).toBe(false); expect($form.count.$valid).toBe(false);
expect($form.$valid).toBe(false); expect($form.$valid).toBe(false);
}); });
it('should pass validation if count increased to 2 and min is 1', function() { it('should pass validation if count increased to 2 and min is 1', function () {
$form.count.$setViewValue(2); $form.count.$setViewValue(2);
$scope.$digest(); $scope.$digest();
expect($scope.count).toBe(2); expect($scope.count).toBe(2);
@ -88,16 +84,15 @@
expect($form.$valid).toBe(true); expect($form.$valid).toBe(true);
}); });
it('should pass validation if count is empty', function() { it('should pass validation if count is empty', function () {
$form.count.$setViewValue(''); $form.count.$setViewValue('');
$scope.$digest(); $scope.$digest();
expect($form.count.$valid).toBe(true); expect($form.count.$valid).toBe(true);
expect($form.$valid).toBe(true); expect($form.$valid).toBe(true);
}); });
}); });
describe('hzPasswordMatch directive', function() { describe('hzPasswordMatch directive', function () {
var $compile, $rootScope; var $compile, $rootScope;
var element, password, cpassword; var element, password, cpassword;
@ -108,7 +103,7 @@
'hz-password-match="form.password">' + 'hz-password-match="form.password">' +
'</form>'; '</form>';
beforeEach(inject(function($injector){ beforeEach(inject(function ($injector) {
$compile = $injector.get('$compile'); $compile = $injector.get('$compile');
$rootScope = $injector.get('$rootScope').$new(); $rootScope = $injector.get('$rootScope').$new();
@ -122,60 +117,57 @@
$rootScope.$digest(); $rootScope.$digest();
})); }));
it('should be initially empty', function() { it('should be initially empty', function () {
expect(password.val()).toEqual(''); expect(password.val()).toEqual('');
expect(password.val()).toEqual(cpassword.val()); expect(password.val()).toEqual(cpassword.val());
expect(cpassword.hasClass('ng-valid')).toBe(true); expect(cpassword.hasClass('ng-valid')).toBe(true);
}); });
it('should not match if user changes only password', function(done) { it('should not match if user changes only password', function (done) {
$rootScope.user.password = 'password'; $rootScope.user.password = 'password';
$rootScope.$digest(); $rootScope.$digest();
cpassword.change(); cpassword.change();
setTimeout(function(){ setTimeout(function () {
expect(cpassword.val()).not.toEqual(password.val()); expect(cpassword.val()).not.toEqual(password.val());
expect(cpassword.hasClass('ng-invalid')).toBe(true); expect(cpassword.hasClass('ng-invalid')).toBe(true);
done(); done();
}, 1000); }, 1000);
}); });
it('should not match if user changes only confirmation password', function(done) { it('should not match if user changes only confirmation password', function (done) {
$rootScope.user.cpassword = 'password'; $rootScope.user.cpassword = 'password';
$rootScope.$digest(); $rootScope.$digest();
cpassword.change(); cpassword.change();
setTimeout(function(){ setTimeout(function () {
expect(cpassword.val()).not.toEqual(password.val()); expect(cpassword.val()).not.toEqual(password.val());
expect(cpassword.hasClass('ng-invalid')).toBe(true); expect(cpassword.hasClass('ng-invalid')).toBe(true);
done(); done();
}, 1000); }, 1000);
}); });
it('should match if both passwords are the same', function(done) { it('should match if both passwords are the same', function (done) {
$rootScope.user.password = 'password'; $rootScope.user.password = 'password';
$rootScope.user.cpassword = 'password'; $rootScope.user.cpassword = 'password';
$rootScope.$digest(); $rootScope.$digest();
cpassword.change(); cpassword.change();
setTimeout(function(){ setTimeout(function () {
expect(cpassword.val()).toEqual(password.val()); expect(cpassword.val()).toEqual(password.val());
expect(cpassword.hasClass('ng-valid')).toBe(true); expect(cpassword.hasClass('ng-valid')).toBe(true);
done(); done();
}, 1000); }, 1000);
}); });
it('should not match if both passwords are different', function(done) { it('should not match if both passwords are different', function (done) {
$rootScope.user.password = 'password123'; $rootScope.user.password = 'password123';
$rootScope.user.cpassword = 'password345'; $rootScope.user.cpassword = 'password345';
$rootScope.$digest(); $rootScope.$digest();
cpassword.change(); cpassword.change();
setTimeout(function(){ setTimeout(function () {
expect(cpassword.val()).not.toEqual(password.val()); expect(cpassword.val()).not.toEqual(password.val());
expect(cpassword.hasClass('ng-invalid')).toBe(true); expect(cpassword.hasClass('ng-invalid')).toBe(true);
done(); done();
}, 1000); }, 1000);
}); });
}); // end of hzPasswordMatch directive }); // end of hzPasswordMatch directive
}); });
})();
})();

View File

@ -97,5 +97,4 @@
}; };
} }
]); ]);
})(); })();

View File

@ -8,18 +8,16 @@
}); });
describe('workflow factory', function () { describe('workflow factory', function () {
var workflow, spec;
var workflow, var decorators = [
spec, function (spec) {
decorators = [ angular.forEach(spec.steps, function (step) {
function (spec) { if (step.requireSomeServices) {
angular.forEach(spec.steps, function (step) { step.checkReadiness = function () {};
if (step.requireSomeServices) {
step.checkReadiness = function () {};
}
});
} }
]; });
}
];
beforeEach(module('horizon.framework.util.workflow')); beforeEach(module('horizon.framework.util.workflow'));
@ -55,5 +53,4 @@
expect(angular.isFunction(steps[2].checkReadiness)).toBe(true); expect(angular.isFunction(steps[2].checkReadiness)).toBe(true);
}); });
}); });
})(); })();

View File

@ -1,5 +1,5 @@
(function () { (function () {
'use strict'; 'use strict';
angular.module('horizon.framework.widgets', [ angular.module('horizon.framework.widgets', [
'horizon.framework.widgets.help-panel', 'horizon.framework.widgets.help-panel',