diff options
-rw-r--r-- | app/assets/javascripts/emoji/no_emoji_validator.js | 44 | ||||
-rw-r--r-- | app/assets/javascripts/pages/sessions/new/index.js | 2 | ||||
-rw-r--r-- | app/assets/javascripts/pages/sessions/new/length_validator.js | 32 | ||||
-rw-r--r-- | app/assets/javascripts/validators/input_validator.js | 34 | ||||
-rw-r--r-- | app/assets/stylesheets/pages/login.scss | 3 | ||||
-rw-r--r-- | app/views/devise/shared/_signup_box.html.haml | 12 | ||||
-rw-r--r-- | changelogs/unreleased/60323-inline-validation-for-users-name-and-username-length.yml | 5 | ||||
-rw-r--r-- | locale/gitlab.pot | 6 | ||||
-rw-r--r-- | spec/features/users/signup_spec.rb | 44 |
9 files changed, 138 insertions, 44 deletions
diff --git a/app/assets/javascripts/emoji/no_emoji_validator.js b/app/assets/javascripts/emoji/no_emoji_validator.js index 0fd4dd74953..384d62a133a 100644 --- a/app/assets/javascripts/emoji/no_emoji_validator.js +++ b/app/assets/javascripts/emoji/no_emoji_validator.js @@ -1,10 +1,11 @@ import { __ } from '~/locale'; import emojiRegex from 'emoji-regex'; +import InputValidator from '../validators/input_validator'; -const invalidInputClass = 'gl-field-error-outline'; - -export default class NoEmojiValidator { +export default class NoEmojiValidator extends InputValidator { constructor(opts = {}) { + super(); + const container = opts.container || ''; this.noEmojiEmelents = document.querySelectorAll(`${container} .js-block-emoji`); @@ -19,45 +20,14 @@ export default class NoEmojiValidator { const { value } = this.inputDomElement; + this.errorMessage = __('Invalid input, please avoid emojis'); + this.validatePattern(value); this.setValidationStateAndMessage(); } validatePattern(value) { const pattern = emojiRegex(); - this.hasEmojis = new RegExp(pattern).test(value); - - if (this.hasEmojis) { - this.inputDomElement.setCustomValidity(__('Invalid input, please avoid emojis')); - } else { - this.inputDomElement.setCustomValidity(''); - } - } - - setValidationStateAndMessage() { - if (!this.inputDomElement.checkValidity()) { - this.setInvalidState(); - } else { - this.clearFieldValidationState(); - } - } - - clearFieldValidationState() { - this.inputDomElement.classList.remove(invalidInputClass); - this.inputErrorMessage.classList.add('hide'); - } - - setInvalidState() { - this.inputDomElement.classList.add(invalidInputClass); - this.setErrorMessage(); - } - - setErrorMessage() { - if (this.hasEmojis) { - this.inputErrorMessage.innerHTML = this.inputDomElement.validationMessage; - } else { - this.inputErrorMessage.innerHTML = this.inputDomElement.title; - } - this.inputErrorMessage.classList.remove('hide'); + this.invalidInput = new RegExp(pattern).test(value); } } diff --git a/app/assets/javascripts/pages/sessions/new/index.js b/app/assets/javascripts/pages/sessions/new/index.js index e1a3f42a71f..3f5a3e15c2c 100644 --- a/app/assets/javascripts/pages/sessions/new/index.js +++ b/app/assets/javascripts/pages/sessions/new/index.js @@ -1,4 +1,5 @@ import $ from 'jquery'; +import LengthValidator from './length_validator'; import UsernameValidator from './username_validator'; import NoEmojiValidator from '../../../emoji/no_emoji_validator'; import SigninTabsMemoizer from './signin_tabs_memoizer'; @@ -6,6 +7,7 @@ import OAuthRememberMe from './oauth_remember_me'; import preserveUrlFragment from './preserve_url_fragment'; document.addEventListener('DOMContentLoaded', () => { + new LengthValidator(); // eslint-disable-line no-new new UsernameValidator(); // eslint-disable-line no-new new SigninTabsMemoizer(); // eslint-disable-line no-new new NoEmojiValidator(); // eslint-disable-line no-new diff --git a/app/assets/javascripts/pages/sessions/new/length_validator.js b/app/assets/javascripts/pages/sessions/new/length_validator.js new file mode 100644 index 00000000000..3d687ca08cc --- /dev/null +++ b/app/assets/javascripts/pages/sessions/new/length_validator.js @@ -0,0 +1,32 @@ +import InputValidator from '../../../validators/input_validator'; + +const errorMessageClass = 'gl-field-error'; + +export default class LengthValidator extends InputValidator { + constructor(opts = {}) { + super(); + + const container = opts.container || ''; + const validateLengthElements = document.querySelectorAll(`${container} .js-validate-length`); + + validateLengthElements.forEach(element => + element.addEventListener('input', this.eventHandler.bind(this)), + ); + } + + eventHandler(event) { + this.inputDomElement = event.target; + this.inputErrorMessage = this.inputDomElement.parentElement.querySelector( + `.${errorMessageClass}`, + ); + + const { value } = this.inputDomElement; + const { maxLengthMessage, maxLength } = this.inputDomElement.dataset; + + this.errorMessage = maxLengthMessage; + + this.invalidInput = value.length > parseInt(maxLength, 10); + + this.setValidationStateAndMessage(); + } +} diff --git a/app/assets/javascripts/validators/input_validator.js b/app/assets/javascripts/validators/input_validator.js new file mode 100644 index 00000000000..f37373977b8 --- /dev/null +++ b/app/assets/javascripts/validators/input_validator.js @@ -0,0 +1,34 @@ +const invalidInputClass = 'gl-field-error-outline'; + +export default class InputValidator { + constructor() { + this.inputDomElement = {}; + this.inputErrorMessage = {}; + this.errorMessage = null; + this.invalidInput = null; + } + + setValidationStateAndMessage() { + this.setValidationMessage(); + + const isInvalidInput = !this.inputDomElement.checkValidity(); + this.inputDomElement.classList.toggle(invalidInputClass, isInvalidInput); + this.inputErrorMessage.classList.toggle('hide', !isInvalidInput); + } + + setValidationMessage() { + if (this.invalidInput) { + this.inputDomElement.setCustomValidity(this.errorMessage); + this.inputErrorMessage.innerHTML = this.errorMessage; + } else { + this.resetValidationMessage(); + } + } + + resetValidationMessage() { + if (this.inputDomElement.validationMessage === this.errorMessage) { + this.inputDomElement.setCustomValidity(''); + this.inputErrorMessage.innerHTML = this.inputDomElement.title; + } + } +} diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index 297f642681b..d8aabecc036 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -73,7 +73,8 @@ .login-body { font-size: 13px; - input + p { + input + p, + input ~ p.field-validation { margin-top: 5px; } diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index 383fd5130ce..5eba819172b 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -1,3 +1,5 @@ +- max_name_length = 128 +- max_username_length = 255 #register-pane.tab-pane.login-box{ role: 'tabpanel' } .login-body = form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name), html: { class: "new_new_user gl-show-field-errors", "aria-live" => "assertive" }) do |f| @@ -5,13 +7,13 @@ = render "devise/shared/error_messages", resource: resource .name.form-group = f.label :name, _('Full name'), class: 'label-bold' - = f.text_field :name, class: "form-control top qa-new-user-name js-block-emoji", required: true, title: _("This field is required.") + = f.text_field :name, class: "form-control top qa-new-user-name js-block-emoji js-validate-length", :data => { :max_length => max_name_length, :max_length_message => s_("SignUp|Name is too long (maximum is %{max_length} characters).") % { max_length: max_name_length } }, required: true, title: _("This field is required.") .username.form-group = f.label :username, class: 'label-bold' - = f.text_field :username, class: "form-control middle qa-new-user-username js-block-emoji", pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: _("Please create a username with only alphanumeric characters.") - %p.validation-error.hide= _('Username is already taken.') - %p.validation-success.hide= _('Username is available.') - %p.validation-pending.hide= _('Checking username availability...') + = f.text_field :username, class: "form-control middle qa-new-user-username js-block-emoji js-validate-length", :data => { :max_length => max_username_length, :max_length_message => s_("SignUp|Username is too long (maximum is %{max_length} characters).") % { max_length: max_username_length } }, pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: _("Please create a username with only alphanumeric characters.") + %p.validation-error.field-validation.hide= _('Username is already taken.') + %p.validation-success.field-validation.hide= _('Username is available.') + %p.validation-pending.field-validation.hide= _('Checking username availability...') .form-group = f.label :email, class: 'label-bold' = f.email_field :email, class: "form-control middle qa-new-user-email", required: true, title: _("Please provide a valid email address.") diff --git a/changelogs/unreleased/60323-inline-validation-for-users-name-and-username-length.yml b/changelogs/unreleased/60323-inline-validation-for-users-name-and-username-length.yml new file mode 100644 index 00000000000..83b7bd3433e --- /dev/null +++ b/changelogs/unreleased/60323-inline-validation-for-users-name-and-username-length.yml @@ -0,0 +1,5 @@ +--- +title: Update registration form to indicate invalid name or username length on input +merge_request: 28095 +author: Jiaan Louw +type: changed diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 39e18147311..6258208234e 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -9130,6 +9130,12 @@ msgstr "" msgid "Sign-up restrictions" msgstr "" +msgid "SignUp|Name is too long (maximum is %{max_length} characters)." +msgstr "" + +msgid "SignUp|Username is too long (maximum is %{max_length} characters)." +msgstr "" + msgid "Signed in" msgstr "" diff --git a/spec/features/users/signup_spec.rb b/spec/features/users/signup_spec.rb index 957c3cfc583..1a9caf0ffbb 100644 --- a/spec/features/users/signup_spec.rb +++ b/spec/features/users/signup_spec.rb @@ -25,6 +25,13 @@ describe 'Signup' do expect(find('.username')).not_to have_css '.gl-field-error-outline' end + it 'does not show an error border if the username length is not longer than 255 characters' do + fill_in 'new_user_username', with: 'u' * 255 + wait_for_requests + + expect(find('.username')).not_to have_css '.gl-field-error-outline' + end + it 'shows an error border if the username already exists' do existing_user = create(:user) @@ -41,6 +48,20 @@ describe 'Signup' do expect(find('.username')).to have_css '.gl-field-error-outline' end + it 'shows an error border if the username is longer than 255 characters' do + fill_in 'new_user_username', with: 'u' * 256 + wait_for_requests + + expect(find('.username')).to have_css '.gl-field-error-outline' + end + + it 'shows an error message if the username is longer than 255 characters' do + fill_in 'new_user_username', with: 'u' * 256 + wait_for_requests + + expect(page).to have_content("Username is too long (maximum is 255 characters).") + end + it 'shows an error message on submit if the username contains special characters' do fill_in 'new_user_username', with: 'new$user!username' wait_for_requests @@ -67,14 +88,35 @@ describe 'Signup' do before do visit root_path click_link 'Register' - simulate_input('#new_user_name', 'Ehsan 🦋') + end + + it 'does not show an error border if the user\'s fullname length is not longer than 128 characters' do + fill_in 'new_user_name', with: 'u' * 128 + + expect(find('.name')).not_to have_css '.gl-field-error-outline' end it 'shows an error border if the user\'s fullname contains an emoji' do + simulate_input('#new_user_name', 'Ehsan 🦋') + + expect(find('.name')).to have_css '.gl-field-error-outline' + end + + it 'shows an error border if the user\'s fullname is longer than 128 characters' do + fill_in 'new_user_name', with: 'n' * 129 + expect(find('.name')).to have_css '.gl-field-error-outline' end + it 'shows an error message if the user\'s fullname is longer than 128 characters' do + fill_in 'new_user_name', with: 'n' * 129 + + expect(page).to have_content("Name is too long (maximum is 128 characters).") + end + it 'shows an error message if the username contains emojis' do + simulate_input('#new_user_name', 'Ehsan 🦋') + expect(page).to have_content("Invalid input, please avoid emojis") end end |