From bedd6cc2f50b597f642d7ce53746343a574310e3 Mon Sep 17 00:00:00 2001 From: Renzo Lucioni Date: Thu, 6 Nov 2014 18:12:46 -0500 Subject: [PATCH] Jasmine tests of validation utility Includes a modification of validation logic which performs min and max length checks on optional fields in addition to required fields. --- .../djangoapps/user_api/tests/test_views.py | 1 - .../static/js/spec/edx.utils.validate_spec.js | 166 ++++++++++++++++++ .../edx.utils.validate.js | 20 ++- .../rwd_header_footer.js | 0 common/static/js_test.yml | 2 + lms/envs/common.py | 4 +- 6 files changed, 185 insertions(+), 8 deletions(-) create mode 100644 common/static/js/spec/edx.utils.validate_spec.js rename common/static/js/{spec_helpers => utils}/edx.utils.validate.js (89%) rename common/static/js/{spec_helpers => utils}/rwd_header_footer.js (100%) diff --git a/common/djangoapps/user_api/tests/test_views.py b/common/djangoapps/user_api/tests/test_views.py index 01ed1be80c..4b0c67a159 100644 --- a/common/djangoapps/user_api/tests/test_views.py +++ b/common/djangoapps/user_api/tests/test_views.py @@ -720,7 +720,6 @@ class LoginSessionViewTest(ApiTestCase): # Missing both email and password response = self.client.post(self.url, {}) - #COMEBACK @ddt.ddt diff --git a/common/static/js/spec/edx.utils.validate_spec.js b/common/static/js/spec/edx.utils.validate_spec.js new file mode 100644 index 0000000000..a755a16c5d --- /dev/null +++ b/common/static/js/spec/edx.utils.validate_spec.js @@ -0,0 +1,166 @@ +describe('edx.utils.validate', function () { + 'use strict'; + + var fixture = null, + field = null, + result = null, + MIN_LENGTH = 2, + MAX_LENGTH = 20, + VALID_STRING = 'xsy_is_awesome', + SHORT_STRING = 'x', + LONG_STRING = 'xsy_is_way_too_awesome', + REQUIRED_ERROR_FRAGMENT = 'required', + MIN_ERROR_FRAGMENT = 'least', + MAX_ERROR_FRAGMENT = 'up to', + CUSTOM_MESSAGE = 'custom message'; + + var createFixture = function( type, name, required, minlength, maxlength, value ) { + setFixtures(''); + + field = $('#field'); + field.prop('required', required); + field.attr({ + name: name, + minlength: minlength, + maxlength: maxlength, + value: value + }); + }; + + var expectValid = function() { + result = edx.utils.validate(field); + expect(result.isValid).toBe(true); + }; + + var expectInvalid = function( errorFragment ) { + result = edx.utils.validate(field); + expect(result.isValid).toBe(false); + expect(result.message).toMatch(errorFragment); + }; + + it('succeeds if an optional field is left blank', function () { + createFixture('text', 'username', false, MIN_LENGTH, MAX_LENGTH, ''); + expectValid(); + }); + + it('succeeds if a required field is provided a valid value', function () { + createFixture('text', 'username', true, MIN_LENGTH, MAX_LENGTH, VALID_STRING); + expectValid(); + }); + + it('fails if a required field is left blank', function () { + createFixture('text', 'username', true, MIN_LENGTH, MAX_LENGTH, ''); + expectInvalid(REQUIRED_ERROR_FRAGMENT); + }); + + it('fails if a field is provided a value below its minimum character limit', function () { + createFixture('text', 'username', false, MIN_LENGTH, MAX_LENGTH, SHORT_STRING); + + // Verify optional field behavior + expectInvalid(MIN_ERROR_FRAGMENT); + + // Verify required field behavior + field.prop('required', true); + expectInvalid(MIN_ERROR_FRAGMENT); + }); + + it('succeeds if a field with no minimum character limit is provided a value below its maximum character limit', function () { + createFixture('text', 'username', false, null, MAX_LENGTH, SHORT_STRING); + + // Verify optional field behavior + expectValid(); + + // Verify required field behavior + field.prop('required', true); + expectValid(); + }); + + it('fails if a required field with no minimum character limit is left blank', function () { + createFixture('text', 'username', true, null, MAX_LENGTH, ''); + expectInvalid(REQUIRED_ERROR_FRAGMENT); + }); + + it('fails if a field is provided a value above its maximum character limit', function () { + createFixture('text', 'username', false, MIN_LENGTH, MAX_LENGTH, LONG_STRING); + + // Verify optional field behavior + expectInvalid(MAX_ERROR_FRAGMENT); + + // Verify required field behavior + field.prop('required', true); + expectInvalid(MAX_ERROR_FRAGMENT); + }); + + it('succeeds if a field with no maximum character limit is provided a value above its minimum character limit', function () { + createFixture('text', 'username', false, MIN_LENGTH, null, LONG_STRING); + + // Verify optional field behavior + expectValid(); + + // Verify required field behavior + field.prop('required', true); + expectValid(); + }); + + it('succeeds if a field with no character limits is provided a value', function () { + createFixture('text', 'username', false, null, null, VALID_STRING); + + // Verify optional field behavior + expectValid(); + + // Verify required field behavior + field.prop('required', true); + expectValid(); + }); + + it('fails if an email field is provided an invalid address', function () { + createFixture('email', 'email', false, MIN_LENGTH, MAX_LENGTH, 'localpart'); + + // Verify optional field behavior + expectInvalid('invalid'); + + // Verify required field behavior + field.prop('required', false); + expectInvalid('invalid'); + }); + + it('succeeds if an email field is provided a valid address', function () { + createFixture('email', 'email', false, MIN_LENGTH, MAX_LENGTH, 'localpart@label.tld'); + + // Verify optional field behavior + expectValid(); + + // Verify required field behavior + field.prop('required', true); + expectValid(); + }); + + it('succeeds if a checkbox is optional, or required and checked, but fails if a required checkbox is unchecked', function () { + createFixture('checkbox', 'checkbox', false, null, null, 'value'); + + // Optional, unchecked + expectValid(); + + // Optional, checked + field.prop('checked', true); + expectValid(); + + // Required, checked + field.prop('required', true); + expectValid(); + + // Required, unchecked + field.prop('checked', false); + expectInvalid(REQUIRED_ERROR_FRAGMENT); + }); + + it('returns a custom error message if an invalid field has one attached', function () { + // Create a blank required field + createFixture('text', 'username', true, MIN_LENGTH, MAX_LENGTH, ''); + + // Attach a custom error message to the field + field.data('errormsg-required', CUSTOM_MESSAGE); + + expectInvalid(CUSTOM_MESSAGE); + }); +}); diff --git a/common/static/js/spec_helpers/edx.utils.validate.js b/common/static/js/utils/edx.utils.validate.js similarity index 89% rename from common/static/js/spec_helpers/edx.utils.validate.js rename to common/static/js/utils/edx.utils.validate.js index f478219004..45868a8554 100644 --- a/common/static/js/spec_helpers/edx.utils.validate.js +++ b/common/static/js/utils/edx.utils.validate.js @@ -1,8 +1,16 @@ var edx = edx || {}; -(function( $, _, gettext ) { +(function( $, _, _s, gettext ) { 'use strict'; + /* Mix non-conflicting functions from underscore.string + * (all but include, contains, and reverse) into the + * Underscore namespace. In practice, this mixin is done + * by the access view, but doing it here helps keep the + * utility self-contained. + */ + _.mixin( _.str.exports() ); + edx.utils = edx.utils || {}; var utils = (function(){ @@ -35,6 +43,8 @@ var edx = edx || {}; email = _fn.validate.email.valid( $el ); } } else if ( !isBlank ) { + min = _fn.validate.str.minlength( $el ); + max = _fn.validate.str.maxlength( $el ); email = _fn.validate.email.valid( $el ); } @@ -77,7 +87,7 @@ var edx = edx || {}; }, isBlank: function( $el ) { - return ( $el.attr('type') === 'checkbox' ) ? !$el.prop('checked') : !$el.val(); + return ( $el.attr('type') === 'checkbox' ) ? !$el.prop('checked') : !$el.val(); }, email: { @@ -92,7 +102,7 @@ var edx = edx || {}; ), valid: function( $el ) { - return $el.attr('type') === 'email' ? _fn.validate.email.format( $el.val() ) : true; + return $el.attr('type') === 'email' ? _fn.validate.email.format( $el.val() ) : true; }, format: function( str ) { @@ -112,7 +122,7 @@ var edx = edx || {}; name = $el.attr('name'); customMsg = $el.data('errormsg-' + key) || false; - // If the field has a custom error msg attached use it + // If the field has a custom error msg attached, use it if ( customMsg ) { tpl = _fn.validate.msg.custom; @@ -154,4 +164,4 @@ var edx = edx || {}; edx.utils.validate = utils.validate; -})( jQuery, _, gettext ); +})( jQuery, _, _.str, gettext ); diff --git a/common/static/js/spec_helpers/rwd_header_footer.js b/common/static/js/utils/rwd_header_footer.js similarity index 100% rename from common/static/js/spec_helpers/rwd_header_footer.js rename to common/static/js/utils/rwd_header_footer.js diff --git a/common/static/js_test.yml b/common/static/js_test.yml index d6f0db4b7f..b83c3f6bd1 100644 --- a/common/static/js_test.yml +++ b/common/static/js_test.yml @@ -34,6 +34,7 @@ lib_paths: - js/vendor/jquery.truncate.js - js/vendor/mustache.js - js/vendor/underscore-min.js + - js/vendor/underscore.string.min.js - js/vendor/backbone-min.js - js/vendor/jquery.timeago.js - js/vendor/URI.min.js @@ -46,6 +47,7 @@ lib_paths: src_paths: - coffee/src - js/src + - js/utils - js/capa/src # Paths to spec (test) JavaScript files diff --git a/lms/envs/common.py b/lms/envs/common.py index 5bf7d76e19..308a068c5e 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -1032,8 +1032,8 @@ instructor_dash_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/ins # These are not courseware, so they do not need many of the courseware-specific # JavaScript modules. student_account_js = [ - 'js/common_helpers/rwd_header_footer.js', - 'js/common_helpers/edx.utils.validate.js', + 'js/utils/rwd_header_footer.js', + 'js/utils/edx.utils.validate.js', 'js/student_account/enrollment_interface.js', 'js/student_account/models/LoginModel.js', 'js/student_account/models/RegisterModel.js',