Commit d91612ea authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '60323-inline-validation-for-users-name-and-username-length' into 'master'

Resolve "Inline validation for user's name and username length"

Closes #60323

See merge request gitlab-org/gitlab-ce!28095
parents 5caffe48 c38ea7e8
import { __ } from '~/locale'; import { __ } from '~/locale';
import emojiRegex from 'emoji-regex'; import emojiRegex from 'emoji-regex';
import InputValidator from '../validators/input_validator';
const invalidInputClass = 'gl-field-error-outline'; export default class NoEmojiValidator extends InputValidator {
export default class NoEmojiValidator {
constructor(opts = {}) { constructor(opts = {}) {
super();
const container = opts.container || ''; const container = opts.container || '';
this.noEmojiEmelents = document.querySelectorAll(`${container} .js-block-emoji`); this.noEmojiEmelents = document.querySelectorAll(`${container} .js-block-emoji`);
...@@ -19,45 +20,14 @@ export default class NoEmojiValidator { ...@@ -19,45 +20,14 @@ export default class NoEmojiValidator {
const { value } = this.inputDomElement; const { value } = this.inputDomElement;
this.errorMessage = __('Invalid input, please avoid emojis');
this.validatePattern(value); this.validatePattern(value);
this.setValidationStateAndMessage(); this.setValidationStateAndMessage();
} }
validatePattern(value) { validatePattern(value) {
const pattern = emojiRegex(); const pattern = emojiRegex();
this.hasEmojis = new RegExp(pattern).test(value); this.invalidInput = 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');
} }
} }
import $ from 'jquery'; import $ from 'jquery';
import LengthValidator from './length_validator';
import UsernameValidator from './username_validator'; import UsernameValidator from './username_validator';
import NoEmojiValidator from '../../../emoji/no_emoji_validator'; import NoEmojiValidator from '../../../emoji/no_emoji_validator';
import SigninTabsMemoizer from './signin_tabs_memoizer'; import SigninTabsMemoizer from './signin_tabs_memoizer';
...@@ -6,6 +7,7 @@ import OAuthRememberMe from './oauth_remember_me'; ...@@ -6,6 +7,7 @@ import OAuthRememberMe from './oauth_remember_me';
import preserveUrlFragment from './preserve_url_fragment'; import preserveUrlFragment from './preserve_url_fragment';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
new LengthValidator(); // eslint-disable-line no-new
new UsernameValidator(); // eslint-disable-line no-new new UsernameValidator(); // eslint-disable-line no-new
new SigninTabsMemoizer(); // eslint-disable-line no-new new SigninTabsMemoizer(); // eslint-disable-line no-new
new NoEmojiValidator(); // eslint-disable-line no-new new NoEmojiValidator(); // eslint-disable-line no-new
......
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();
}
}
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;
}
}
}
...@@ -73,7 +73,8 @@ ...@@ -73,7 +73,8 @@
.login-body { .login-body {
font-size: 13px; font-size: 13px;
input + p { input + p,
input ~ p.field-validation {
margin-top: 5px; margin-top: 5px;
} }
......
- max_name_length = 128
- max_username_length = 255
#register-pane.tab-pane.login-box{ role: 'tabpanel' } #register-pane.tab-pane.login-box{ role: 'tabpanel' }
.login-body .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| = 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 @@ ...@@ -5,13 +7,13 @@
= render "devise/shared/error_messages", resource: resource = render "devise/shared/error_messages", resource: resource
.name.form-group .name.form-group
= f.label :name, _('Full name'), class: 'label-bold' = 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 .username.form-group
= f.label :username, class: 'label-bold' = 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.") = 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.hide= _('Username is already taken.') %p.validation-error.field-validation.hide= _('Username is already taken.')
%p.validation-success.hide= _('Username is available.') %p.validation-success.field-validation.hide= _('Username is available.')
%p.validation-pending.hide= _('Checking username availability...') %p.validation-pending.field-validation.hide= _('Checking username availability...')
.form-group .form-group
= f.label :email, class: 'label-bold' = 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.") = f.email_field :email, class: "form-control middle qa-new-user-email", required: true, title: _("Please provide a valid email address.")
......
---
title: Update registration form to indicate invalid name or username length on input
merge_request: 28095
author: Jiaan Louw
type: changed
...@@ -9130,6 +9130,12 @@ msgstr "" ...@@ -9130,6 +9130,12 @@ msgstr ""
msgid "Sign-up restrictions" msgid "Sign-up restrictions"
msgstr "" 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" msgid "Signed in"
msgstr "" msgstr ""
......
...@@ -25,6 +25,13 @@ describe 'Signup' do ...@@ -25,6 +25,13 @@ describe 'Signup' do
expect(find('.username')).not_to have_css '.gl-field-error-outline' expect(find('.username')).not_to have_css '.gl-field-error-outline'
end 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 it 'shows an error border if the username already exists' do
existing_user = create(:user) existing_user = create(:user)
...@@ -41,6 +48,20 @@ describe 'Signup' do ...@@ -41,6 +48,20 @@ describe 'Signup' do
expect(find('.username')).to have_css '.gl-field-error-outline' expect(find('.username')).to have_css '.gl-field-error-outline'
end 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 it 'shows an error message on submit if the username contains special characters' do
fill_in 'new_user_username', with: 'new$user!username' fill_in 'new_user_username', with: 'new$user!username'
wait_for_requests wait_for_requests
...@@ -67,14 +88,35 @@ describe 'Signup' do ...@@ -67,14 +88,35 @@ describe 'Signup' do
before do before do
visit root_path visit root_path
click_link 'Register' 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 end
it 'shows an error border if the user\'s fullname contains an emoji' do 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' expect(find('.name')).to have_css '.gl-field-error-outline'
end 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 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") expect(page).to have_content("Invalid input, please avoid emojis")
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment