Merge "JSCS cleanup - horizon/static/framework conf and util"
This commit is contained in:
commit
bf9acb39a5
@ -1,10 +1,10 @@
|
||||
/*global angular*/
|
||||
(function () {
|
||||
'use strict';
|
||||
angular.module('horizon.framework.conf', [])
|
||||
|
||||
angular
|
||||
.module('horizon.framework.conf', [])
|
||||
.constant('horizon.framework.conf.spinner_options', {
|
||||
inline: {
|
||||
inline: {
|
||||
lines: 10,
|
||||
length: 5,
|
||||
width: 2,
|
||||
@ -14,7 +14,7 @@
|
||||
trail: 50,
|
||||
zIndex: 100
|
||||
},
|
||||
modal: {
|
||||
modal: {
|
||||
lines: 10,
|
||||
length: 15,
|
||||
width: 4,
|
||||
@ -33,4 +33,4 @@
|
||||
trail: 50
|
||||
}
|
||||
});
|
||||
}());
|
||||
})();
|
||||
|
@ -1,39 +1,41 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
angular.module('horizon.framework', [
|
||||
'horizon.framework.conf',
|
||||
'horizon.framework.util',
|
||||
'horizon.framework.widgets'
|
||||
])
|
||||
.constant('horizon.framework.basePath', '/static/framework/')
|
||||
angular
|
||||
.module('horizon.framework', [
|
||||
'horizon.framework.conf',
|
||||
'horizon.framework.util',
|
||||
'horizon.framework.widgets'
|
||||
])
|
||||
.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',
|
||||
function ($interpolateProvider, $httpProvider) {
|
||||
// Http global settings for ease of use
|
||||
$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
|
||||
// allow us to mix angular with django templates
|
||||
$interpolateProvider.startSymbol('{$');
|
||||
$interpolateProvider.endSymbol('$}');
|
||||
|
||||
// Http global settings for ease of use
|
||||
$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';
|
||||
|
||||
// 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');
|
||||
// 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);
|
||||
}
|
||||
return $q.reject(error);
|
||||
}
|
||||
};
|
||||
});
|
||||
}]);
|
||||
};
|
||||
});
|
||||
}
|
||||
]);
|
||||
})();
|
||||
|
@ -1,4 +1,4 @@
|
||||
(function() {
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
@ -41,12 +41,12 @@
|
||||
* </tr>
|
||||
* ```
|
||||
*/
|
||||
.directive('bindScope', function() {
|
||||
.directive('bindScope', function () {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, element, attrs, ctrl, transclude) {
|
||||
link: function (scope, element, attrs, ctrl, transclude) {
|
||||
if (transclude) {
|
||||
transclude(scope, function(clone) {
|
||||
transclude(scope, function (clone) {
|
||||
var detailElt = element.find('[bind-scope-target]');
|
||||
if (detailElt.length) {
|
||||
detailElt.append(clone);
|
||||
@ -56,5 +56,4 @@
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
})();
|
||||
})();
|
||||
|
@ -1,22 +1,21 @@
|
||||
(function() {
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
describe('horizon.framework.util.bind-scope module', function() {
|
||||
it('should have been defined', function() {
|
||||
describe('horizon.framework.util.bind-scope module', function () {
|
||||
it('should have been defined', function () {
|
||||
expect(angular.module('horizon.framework.util.bind-scope')).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('bind-scope directive', function() {
|
||||
|
||||
describe('bind-scope directive', function () {
|
||||
var $scope, $element;
|
||||
|
||||
beforeEach(module('horizon.framework'));
|
||||
beforeEach(module('horizon.framework.widgets'));
|
||||
beforeEach(module('horizon.framework.util.bind-scope'));
|
||||
|
||||
beforeEach(module('horizon.framework.util.bind-scope', function($compileProvider) {
|
||||
$compileProvider.directive('testBindScope', function() {
|
||||
beforeEach(module('horizon.framework.util.bind-scope', function ($compileProvider) {
|
||||
$compileProvider.directive('testBindScope', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
@ -30,7 +29,7 @@
|
||||
});
|
||||
}));
|
||||
|
||||
beforeEach(inject(function($injector) {
|
||||
beforeEach(inject(function ($injector) {
|
||||
var $compile = $injector.get('$compile');
|
||||
$scope = $injector.get('$rootScope').$new();
|
||||
|
||||
@ -48,17 +47,15 @@
|
||||
$scope.$digest();
|
||||
}));
|
||||
|
||||
it('should have 3 list items', function() {
|
||||
it('should have 3 list items', function () {
|
||||
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');
|
||||
expect(listItems[0].textContent.trim()).toBe('cat');
|
||||
expect(listItems[1].textContent.trim()).toBe('dog');
|
||||
expect(listItems[2].textContent.trim()).toBe('fish');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
})();
|
||||
})();
|
||||
|
@ -25,128 +25,128 @@
|
||||
*/
|
||||
angular.module('horizon.framework.util.filters', ['horizon.framework.util.i18n'])
|
||||
|
||||
/**
|
||||
* @ngdoc filter
|
||||
* @name yesno
|
||||
* @description
|
||||
* Evaluates given input for standard truthiness and returns translation
|
||||
* of 'Yes' and 'No' for true/false respectively.
|
||||
*/
|
||||
.filter('yesno', ['horizon.framework.util.i18n.gettext', function(gettext) {
|
||||
return function(input) {
|
||||
return (input ? gettext("Yes") : gettext("No"));
|
||||
};
|
||||
}])
|
||||
/**
|
||||
* @ngdoc filter
|
||||
* @name yesno
|
||||
* @description
|
||||
* Evaluates given input for standard truthiness and returns translation
|
||||
* of 'Yes' and 'No' for true/false respectively.
|
||||
*/
|
||||
.filter('yesno', ['horizon.framework.util.i18n.gettext', function (gettext) {
|
||||
return function (input) {
|
||||
return (input ? gettext("Yes") : gettext("No"));
|
||||
};
|
||||
}])
|
||||
|
||||
/**
|
||||
* @ngdoc filter
|
||||
* @name gb
|
||||
* @description
|
||||
* Expects numeric value and suffixes translated 'GB' with spacing.
|
||||
* Returns empty string if input is not a number or is null.
|
||||
*/
|
||||
.filter('gb', function() {
|
||||
return function(input) {
|
||||
if (isNaN(input) || null === input) {
|
||||
return '';
|
||||
} else {
|
||||
return interpolate(gettext("%s GB"), [input.toString()]);
|
||||
}
|
||||
};
|
||||
})
|
||||
/**
|
||||
* @ngdoc filter
|
||||
* @name gb
|
||||
* @description
|
||||
* Expects numeric value and suffixes translated 'GB' with spacing.
|
||||
* Returns empty string if input is not a number or is null.
|
||||
*/
|
||||
.filter('gb', function () {
|
||||
return function (input) {
|
||||
if (isNaN(input) || null === input) {
|
||||
return '';
|
||||
} else {
|
||||
return interpolate(gettext("%s GB"), [input.toString()]);
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
/**
|
||||
* @ngdoc filter
|
||||
* @name mb
|
||||
* @description
|
||||
* Expects numeric value and suffixes translated 'MB' with spacing.
|
||||
* Returns empty string if input is not a number or is null.
|
||||
*/
|
||||
.filter('mb', function() {
|
||||
return function(input) {
|
||||
if (isNaN(input) || null === input) {
|
||||
return '';
|
||||
} else {
|
||||
return interpolate(gettext("%s MB"), [input.toString()]);
|
||||
}
|
||||
};
|
||||
})
|
||||
/**
|
||||
* @ngdoc filter
|
||||
* @name mb
|
||||
* @description
|
||||
* Expects numeric value and suffixes translated 'MB' with spacing.
|
||||
* Returns empty string if input is not a number or is null.
|
||||
*/
|
||||
.filter('mb', function () {
|
||||
return function (input) {
|
||||
if (isNaN(input) || null === input) {
|
||||
return '';
|
||||
} else {
|
||||
return interpolate(gettext("%s MB"), [input.toString()]);
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
/**
|
||||
* @ngdoc filter
|
||||
* @name title
|
||||
* @description
|
||||
* Capitalizes leading characters of individual words.
|
||||
*/
|
||||
.filter('title', function() {
|
||||
return function(input) {
|
||||
if (typeof input !== 'string') {
|
||||
return input;
|
||||
}
|
||||
return input.replace(/(?:^|\s)\S/g, function(a) {
|
||||
return a.toUpperCase();
|
||||
});
|
||||
};
|
||||
})
|
||||
/**
|
||||
* @ngdoc filter
|
||||
* @name title
|
||||
* @description
|
||||
* Capitalizes leading characters of individual words.
|
||||
*/
|
||||
.filter('title', function () {
|
||||
return function (input) {
|
||||
if (typeof input !== 'string') {
|
||||
return input;
|
||||
}
|
||||
return input.replace(/(?:^|\s)\S/g, function (a) {
|
||||
return a.toUpperCase();
|
||||
});
|
||||
};
|
||||
})
|
||||
|
||||
/**
|
||||
* @ngdoc filter
|
||||
* @name noUnderscore
|
||||
* @description
|
||||
* Replaces all underscores with spaces.
|
||||
*/
|
||||
.filter('noUnderscore', function() {
|
||||
return function(input) {
|
||||
if (typeof input !== 'string') {
|
||||
return input;
|
||||
}
|
||||
return input.replace(/_/g, ' ');
|
||||
};
|
||||
})
|
||||
/**
|
||||
* @ngdoc filter
|
||||
* @name noUnderscore
|
||||
* @description
|
||||
* Replaces all underscores with spaces.
|
||||
*/
|
||||
.filter('noUnderscore', function () {
|
||||
return function (input) {
|
||||
if (typeof input !== 'string') {
|
||||
return input;
|
||||
}
|
||||
return input.replace(/_/g, ' ');
|
||||
};
|
||||
})
|
||||
|
||||
/**
|
||||
* @ngdoc filter
|
||||
* @name decode
|
||||
* @description
|
||||
* Returns values based on key and given mapping. If key doesn't exist
|
||||
* in given mapping, return key. This is useful when translations for
|
||||
* codes are present.
|
||||
*/
|
||||
.filter('decode', function() {
|
||||
return function(input, mapping) {
|
||||
var val = mapping[input];
|
||||
return angular.isDefined(val) ? val : input;
|
||||
};
|
||||
})
|
||||
/**
|
||||
* @ngdoc filter
|
||||
* @name decode
|
||||
* @description
|
||||
* Returns values based on key and given mapping. If key doesn't exist
|
||||
* in given mapping, return key. This is useful when translations for
|
||||
* codes are present.
|
||||
*/
|
||||
.filter('decode', function () {
|
||||
return function (input, mapping) {
|
||||
var val = mapping[input];
|
||||
return angular.isDefined(val) ? val : input;
|
||||
};
|
||||
})
|
||||
|
||||
/**
|
||||
* @ngdoc filter
|
||||
* @name bytes
|
||||
* @description
|
||||
* Returns a human-readable approximation of the input of bytes,
|
||||
* converted to a useful unit of measure. Uses 1024-based notation.
|
||||
*/
|
||||
.filter('bytes', function() {
|
||||
return function(input) {
|
||||
var kb = 1024;
|
||||
var mb = kb*1024;
|
||||
var gb = mb*1024;
|
||||
var tb = gb*1024;
|
||||
if (isNaN(input) || null === input || input < 0) {
|
||||
return '';
|
||||
} else if (input >= tb) {
|
||||
return interpolate(gettext("%s TB"), [Number(input/tb).toFixed(2)]);
|
||||
} else if (input >= gb) {
|
||||
return interpolate(gettext("%s GB"), [Number(input/gb).toFixed(2)]);
|
||||
} else if (input >= mb) {
|
||||
return interpolate(gettext("%s MB"), [Number(input/mb).toFixed(2)]);
|
||||
} else if (input >= kb) {
|
||||
return interpolate(gettext("%s KB"), [Number(input/kb).toFixed(2)]);
|
||||
} else {
|
||||
return interpolate(gettext("%s bytes"), [Math.floor(input)]);
|
||||
}
|
||||
};
|
||||
})
|
||||
/**
|
||||
* @ngdoc filter
|
||||
* @name bytes
|
||||
* @description
|
||||
* Returns a human-readable approximation of the input of bytes,
|
||||
* converted to a useful unit of measure. Uses 1024-based notation.
|
||||
*/
|
||||
.filter('bytes', function () {
|
||||
return function (input) {
|
||||
var kb = 1024;
|
||||
var mb = kb * 1024;
|
||||
var gb = mb * 1024;
|
||||
var tb = gb * 1024;
|
||||
if (isNaN(input) || null === input || input < 0) {
|
||||
return '';
|
||||
} else if (input >= tb) {
|
||||
return interpolate(gettext("%s TB"), [Number(input / tb).toFixed(2)]);
|
||||
} else if (input >= gb) {
|
||||
return interpolate(gettext("%s GB"), [Number(input / gb).toFixed(2)]);
|
||||
} else if (input >= mb) {
|
||||
return interpolate(gettext("%s MB"), [Number(input / mb).toFixed(2)]);
|
||||
} else if (input >= kb) {
|
||||
return interpolate(gettext("%s KB"), [Number(input / kb).toFixed(2)]);
|
||||
} else {
|
||||
return interpolate(gettext("%s bytes"), [Math.floor(input)]);
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
/**
|
||||
* @ngdoc filter
|
||||
@ -155,11 +155,11 @@
|
||||
* Displays translated count in table footer.
|
||||
* Takes only finite numbers.
|
||||
*/
|
||||
.filter('itemCount', function() {
|
||||
return function(input) {
|
||||
.filter('itemCount', function () {
|
||||
return function (input) {
|
||||
var isNumeric = (input !== null && isFinite(input));
|
||||
var number = isNumeric ? Math.round(input): 0;
|
||||
var count = (number > 0) ? number: 0;
|
||||
var number = isNumeric ? Math.round(input) : 0;
|
||||
var count = (number > 0) ? number : 0;
|
||||
var format = ngettext('Displaying %s item', 'Displaying %s items', count);
|
||||
return interpolate(format, [count]);
|
||||
};
|
||||
@ -171,11 +171,10 @@
|
||||
* @description
|
||||
* Returns translated text.
|
||||
*/
|
||||
.filter('trans', ['horizon.framework.util.i18n.gettext', function(gettextFunc) {
|
||||
return function(input) {
|
||||
.filter('trans', ['horizon.framework.util.i18n.gettext', function (gettextFunc) {
|
||||
return function (input) {
|
||||
// NOTE: uses 'gettextFunc' to avoid message collection.
|
||||
return gettextFunc(input);
|
||||
};
|
||||
}]);
|
||||
|
||||
}());
|
||||
|
@ -1,220 +1,211 @@
|
||||
describe('horizon.framework.util.filters', function () {
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
beforeEach(module('horizon.framework.util.i18n'));
|
||||
beforeEach(module('horizon.framework.util.filters'));
|
||||
describe('horizon.framework.util.filters', function () {
|
||||
beforeEach(module('horizon.framework.util.i18n'));
|
||||
beforeEach(module('horizon.framework.util.filters'));
|
||||
|
||||
describe('yesno', function () {
|
||||
var yesnoFilter;
|
||||
beforeEach(inject(function (_yesnoFilter_) {
|
||||
yesnoFilter = _yesnoFilter_;
|
||||
}));
|
||||
describe('yesno', function () {
|
||||
var yesnoFilter;
|
||||
beforeEach(inject(function (_yesnoFilter_) {
|
||||
yesnoFilter = _yesnoFilter_;
|
||||
}));
|
||||
|
||||
it('returns Yes for true', function () {
|
||||
expect(yesnoFilter(true)).toBe('Yes');
|
||||
it('returns Yes for true', function () {
|
||||
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 () {
|
||||
expect(yesnoFilter(false)).toBe('No');
|
||||
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('');
|
||||
});
|
||||
});
|
||||
|
||||
it('returns No for null', function () {
|
||||
expect(yesnoFilter(null)).toBe('No');
|
||||
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('');
|
||||
});
|
||||
});
|
||||
|
||||
it('returns No for undefined', function () {
|
||||
expect(yesnoFilter(undefined)).toBe('No');
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
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');
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns No for other falsy values', function () {
|
||||
expect(yesnoFilter(0)).toBe('No');
|
||||
expect(yesnoFilter('')).toBe('No');
|
||||
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");
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
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");
|
||||
}));
|
||||
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
@ -13,43 +13,37 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
}]);
|
||||
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);
|
||||
};
|
||||
}]);
|
||||
})();
|
||||
|
@ -13,52 +13,46 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
describe('horizon.framework.util.i18n', function() {
|
||||
|
||||
describe('horizon.framework.util.i18n', function () {
|
||||
beforeEach(module('horizon.framework.util.i18n'));
|
||||
|
||||
describe('gettext', function() {
|
||||
describe('gettext', function () {
|
||||
var factory;
|
||||
|
||||
describe('Normal operation', function() {
|
||||
|
||||
beforeEach(inject(function($injector) {
|
||||
describe('Normal operation', function () {
|
||||
beforeEach(inject(function ($injector) {
|
||||
factory = $injector.get("horizon.framework.util.i18n.gettext");
|
||||
}));
|
||||
|
||||
it('defines the factory', function() {
|
||||
it('defines the factory', function () {
|
||||
expect(factory).toBeDefined();
|
||||
});
|
||||
|
||||
it('function returns what is given', function() {
|
||||
it('function returns what is given', function () {
|
||||
expect(factory("Hello")).toBe('Hello');
|
||||
});
|
||||
});
|
||||
|
||||
describe("injected window.gettext", function() {
|
||||
|
||||
beforeEach(module(function($provide) {
|
||||
var $window = {gettext: function(x) { return x.replace(/good/, 'bad'); }};
|
||||
describe("injected window.gettext", function () {
|
||||
beforeEach(module(function ($provide) {
|
||||
var $window = { gettext: function (x) { return x.replace(/good/, 'bad'); } };
|
||||
$provide.value('$window', $window);
|
||||
}));
|
||||
|
||||
// Get the factory by name.
|
||||
beforeEach(inject(function($injector) {
|
||||
beforeEach(inject(function ($injector) {
|
||||
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)
|
||||
// indirection. But we can make sure it was called.
|
||||
expect(factory("good cop")).toBe("bad cop");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
})();
|
||||
|
@ -11,5 +11,4 @@
|
||||
'horizon.framework.util.validators'
|
||||
])
|
||||
.constant('horizon.framework.util.basePath', '/static/framework/util/');
|
||||
|
||||
})();
|
||||
|
@ -15,6 +15,7 @@
|
||||
* |---------------------------------------------------------------------------------|
|
||||
* | {@link horizon.framework.util.validators.directive:validateNumberMax `validateNumberMax`} |
|
||||
* | {@link horizon.framework.util.validators.directive:validateNumberMin `validateNumberMin`} |
|
||||
* | {@link horizon.framework.util.validators.directive:notBlank `notBlank`} |
|
||||
* | {@link horizon.framework.util.validators.directive:hzPasswordMatch `hzPasswordMatch`} |
|
||||
*
|
||||
*/
|
||||
@ -47,38 +48,40 @@
|
||||
* validate-number-max="{$ maxNumber $}">
|
||||
* ```
|
||||
*/
|
||||
.directive('validateNumberMax', [function () {
|
||||
return {
|
||||
require: 'ngModel',
|
||||
restrict: 'A',
|
||||
link: function (scope, element, attrs, ctrl) {
|
||||
if (!ctrl) {
|
||||
return;
|
||||
.directive('validateNumberMax', [function () {
|
||||
return {
|
||||
require: 'ngModel',
|
||||
restrict: 'A',
|
||||
link: function (scope, element, attrs, ctrl) {
|
||||
if (!ctrl) {
|
||||
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) {
|
||||
var max = scope.$eval(attrs.validateNumberMax);
|
||||
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;
|
||||
};
|
||||
|
||||
// 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
|
||||
// or model (programmatically)
|
||||
ctrl.$parsers.push(maxValidator);
|
||||
ctrl.$formatters.push(maxValidator);
|
||||
|
||||
attrs.$observe('validateNumberMax', function () {
|
||||
maxValidator(ctrl.$modelValue);
|
||||
});
|
||||
}
|
||||
};
|
||||
}])
|
||||
attrs.$observe('validateNumberMax', function () {
|
||||
maxValidator(ctrl.$modelValue);
|
||||
});
|
||||
}
|
||||
};
|
||||
}])
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
@ -107,107 +110,115 @@
|
||||
* validate-number-min="{$ minNumber $}">
|
||||
* ```
|
||||
*/
|
||||
.directive('validateNumberMin', [function () {
|
||||
return {
|
||||
require: 'ngModel',
|
||||
restrict: 'A',
|
||||
link: function (scope, element, attrs, ctrl) {
|
||||
if (!ctrl) {
|
||||
return;
|
||||
.directive('validateNumberMin', [function () {
|
||||
return {
|
||||
require: 'ngModel',
|
||||
restrict: 'A',
|
||||
link: function (scope, element, attrs, ctrl) {
|
||||
if (!ctrl) {
|
||||
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) {
|
||||
var min = scope.$eval(attrs.validateNumberMin);
|
||||
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;
|
||||
};
|
||||
|
||||
// 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
|
||||
// or model (programmatically)
|
||||
ctrl.$parsers.push(minValidator);
|
||||
ctrl.$formatters.push(minValidator);
|
||||
attrs.$observe('validateNumberMin', function () {
|
||||
minValidator(ctrl.$modelValue);
|
||||
});
|
||||
}
|
||||
};
|
||||
}])
|
||||
|
||||
attrs.$observe('validateNumberMin', function () {
|
||||
minValidator(ctrl.$modelValue);
|
||||
});
|
||||
}
|
||||
};
|
||||
}])
|
||||
|
||||
.directive('notBlank', function () {
|
||||
return {
|
||||
require: 'ngModel',
|
||||
link: function (scope, elm, attrs, ctrl) {
|
||||
ctrl.$parsers.unshift(function (viewValue) {
|
||||
if (viewValue.length) {
|
||||
// it is valid
|
||||
ctrl.$setValidity('notBlank', true);
|
||||
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);
|
||||
});
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name notBlank
|
||||
* @element ng-model
|
||||
* @description Ensure that the value is not blank
|
||||
*/
|
||||
.directive('notBlank', function () {
|
||||
return {
|
||||
require: 'ngModel',
|
||||
link: function (scope, elm, attrs, ctrl) {
|
||||
ctrl.$parsers.unshift(function (viewValue) {
|
||||
if (viewValue.length) {
|
||||
// it is valid
|
||||
ctrl.$setValidity('notBlank', true);
|
||||
return viewValue;
|
||||
}
|
||||
// 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
|
||||
var pwElement = $('#'+scope.pw.$name);
|
||||
pwElement.on('keyup change', passwordCheck);
|
||||
element.on('keyup change', passwordCheck);
|
||||
/**
|
||||
* @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) {
|
||||
|
||||
} // end of link
|
||||
}; // end of return
|
||||
}); // end of directive
|
||||
// helper function to check that password matches
|
||||
function passwordCheck() {
|
||||
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
|
||||
})();
|
||||
|
@ -1,22 +1,20 @@
|
||||
(function() {
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
describe('horizon.framework.util.validators module', function() {
|
||||
it('should have been defined', function() {
|
||||
describe('horizon.framework.util.validators module', function () {
|
||||
it('should have been defined', function () {
|
||||
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.util.validators'));
|
||||
|
||||
describe('validateNumberMax directive', function() {
|
||||
|
||||
describe('validateNumberMax directive', function () {
|
||||
var $scope, $form;
|
||||
|
||||
beforeEach(inject(function($injector) {
|
||||
beforeEach(inject(function ($injector) {
|
||||
var $compile = $injector.get('$compile');
|
||||
$scope = $injector.get('$rootScope').$new();
|
||||
|
||||
@ -33,32 +31,30 @@
|
||||
$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.$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);
|
||||
$scope.$digest();
|
||||
expect($form.count.$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('');
|
||||
$scope.$digest();
|
||||
expect($form.count.$valid).toBe(true);
|
||||
expect($form.$valid).toBe(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('validateNumberMin directive', function() {
|
||||
|
||||
describe('validateNumberMin directive', function () {
|
||||
var $scope, $form;
|
||||
|
||||
beforeEach(inject(function($injector) {
|
||||
beforeEach(inject(function ($injector) {
|
||||
var $compile = $injector.get('$compile');
|
||||
$scope = $injector.get('$rootScope').$new();
|
||||
|
||||
@ -75,12 +71,12 @@
|
||||
$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.$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);
|
||||
$scope.$digest();
|
||||
expect($scope.count).toBe(2);
|
||||
@ -88,16 +84,15 @@
|
||||
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('');
|
||||
$scope.$digest();
|
||||
expect($form.count.$valid).toBe(true);
|
||||
expect($form.$valid).toBe(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('hzPasswordMatch directive', function() {
|
||||
describe('hzPasswordMatch directive', function () {
|
||||
|
||||
var $compile, $rootScope;
|
||||
var element, password, cpassword;
|
||||
@ -108,7 +103,7 @@
|
||||
'hz-password-match="form.password">' +
|
||||
'</form>';
|
||||
|
||||
beforeEach(inject(function($injector){
|
||||
beforeEach(inject(function ($injector) {
|
||||
$compile = $injector.get('$compile');
|
||||
$rootScope = $injector.get('$rootScope').$new();
|
||||
|
||||
@ -122,60 +117,57 @@
|
||||
$rootScope.$digest();
|
||||
}));
|
||||
|
||||
it('should be initially empty', function() {
|
||||
it('should be initially empty', function () {
|
||||
expect(password.val()).toEqual('');
|
||||
expect(password.val()).toEqual(cpassword.val());
|
||||
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.$digest();
|
||||
cpassword.change();
|
||||
setTimeout(function(){
|
||||
setTimeout(function () {
|
||||
expect(cpassword.val()).not.toEqual(password.val());
|
||||
expect(cpassword.hasClass('ng-invalid')).toBe(true);
|
||||
done();
|
||||
}, 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.$digest();
|
||||
cpassword.change();
|
||||
setTimeout(function(){
|
||||
setTimeout(function () {
|
||||
expect(cpassword.val()).not.toEqual(password.val());
|
||||
expect(cpassword.hasClass('ng-invalid')).toBe(true);
|
||||
done();
|
||||
}, 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.cpassword = 'password';
|
||||
$rootScope.$digest();
|
||||
cpassword.change();
|
||||
setTimeout(function(){
|
||||
setTimeout(function () {
|
||||
expect(cpassword.val()).toEqual(password.val());
|
||||
expect(cpassword.hasClass('ng-valid')).toBe(true);
|
||||
done();
|
||||
}, 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.cpassword = 'password345';
|
||||
$rootScope.$digest();
|
||||
cpassword.change();
|
||||
setTimeout(function(){
|
||||
setTimeout(function () {
|
||||
expect(cpassword.val()).not.toEqual(password.val());
|
||||
expect(cpassword.hasClass('ng-invalid')).toBe(true);
|
||||
done();
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
}); // end of hzPasswordMatch directive
|
||||
|
||||
});
|
||||
|
||||
})();
|
||||
})();
|
||||
|
@ -97,5 +97,4 @@
|
||||
};
|
||||
}
|
||||
]);
|
||||
|
||||
})();
|
||||
|
@ -8,18 +8,16 @@
|
||||
});
|
||||
|
||||
describe('workflow factory', function () {
|
||||
|
||||
var workflow,
|
||||
spec,
|
||||
decorators = [
|
||||
function (spec) {
|
||||
angular.forEach(spec.steps, function (step) {
|
||||
if (step.requireSomeServices) {
|
||||
step.checkReadiness = function () {};
|
||||
}
|
||||
});
|
||||
var workflow, spec;
|
||||
var decorators = [
|
||||
function (spec) {
|
||||
angular.forEach(spec.steps, function (step) {
|
||||
if (step.requireSomeServices) {
|
||||
step.checkReadiness = function () {};
|
||||
}
|
||||
];
|
||||
});
|
||||
}
|
||||
];
|
||||
|
||||
beforeEach(module('horizon.framework.util.workflow'));
|
||||
|
||||
@ -55,5 +53,4 @@
|
||||
expect(angular.isFunction(steps[2].checkReadiness)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
})();
|
||||
|
@ -1,5 +1,5 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
'use strict';
|
||||
|
||||
angular.module('horizon.framework.widgets', [
|
||||
'horizon.framework.widgets.help-panel',
|
||||
|
Loading…
Reference in New Issue
Block a user