From 3ac1989c735c1c4c7a0ae9df9b903caaa18b6db3 Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Wed, 8 Jun 2016 08:52:10 +0100 Subject: [PATCH 1/9] Added /u/:username/exists endpoint and added input debouncing for request added error message Renamed file changed error delivery Review fixes --- app/assets/javascripts/dispatcher.js | 2 + app/assets/javascripts/username_validator.es6 | 51 ++++++++++++++++ app/assets/stylesheets/pages/login.scss | 15 +++++ app/controllers/users_controller.rb | 4 ++ app/views/devise/shared/_signup_box.html.haml | 59 ++++++++++--------- config/routes.rb | 1 + 6 files changed, 103 insertions(+), 29 deletions(-) create mode 100644 app/assets/javascripts/username_validator.es6 diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index ba64d2bcf0ba..bf9c865f6ebd 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -22,6 +22,8 @@ switch (page) { case 'projects:boards:show': shortcut_handler = new ShortcutsNavigation(); + case 'signup': + new UsernameValidator(); break; case 'projects:issues:index': Issuable.init(); diff --git a/app/assets/javascripts/username_validator.es6 b/app/assets/javascripts/username_validator.es6 new file mode 100644 index 000000000000..67dacb655cc3 --- /dev/null +++ b/app/assets/javascripts/username_validator.es6 @@ -0,0 +1,51 @@ +((global) => { + class UsernameValidator { + constructor() { + this.debounceTimeoutDuration = 1000; + this.errorIconClasses = 'fa fa-exclamation-circle error'; + this.usernameInUseMessage = 'Username "$1" is in use!'; + this.loadingIconClasses = 'fa fa-spinner fa-spin'; + this.successIconClasses = 'fa fa-check-circle success'; + this.tooltipPlacement = 'left'; + + this.inputElement = $('#new_user_username'); + let inputContainer = this.inputElement.parent(); + inputContainer.append(''); + this.iconElement = $('i', inputContainer); + + let debounceTimeout = _.debounce(this.validateUsername, debounceTimeoutDuration); + this.inputElement.keyup(() => this.debounceRequest(debounceTimeout)); + } + + debounceRequest(debounceTimeout) { + this.iconElement.removeClass().tooltip('destroy'); + let username = this.inputElement.val(); + if (username === '') return; + this.iconElement.addClass(loadingIconClasses); + debounceTimeout(username); + } + + validateUsername(username) { + $.ajax({ + type: 'GET', + url: `/u/${username}/exists`, + dataType: 'json', + success: () => { + this.iconElement + .removeClass().addClass(errorIconClasses) + .tooltip({ + title: usernameInUseMessage.replace(/\$1/g, username), + placement: tooltipPlacement + }); + }, + error: () => { + this.iconElement + .removeClass().addClass(successIconClasses) + .tooltip('destroy') + } + }); + } + } + + global.UsernameValidator = UsernameValidator; +})(window); diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index 403171d4532e..042b17b865a4 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -64,6 +64,21 @@ .nav .active a { background: transparent; } + + .login-body .new_new_user .username { + position: relative; + i { + position: absolute; + right: 8px; + top: 12px; + &.success { + color: $green-normal; + } + &.error { + color: $red-normal; + } + } + } } .form-control { diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index a99632454d94..cfee71e97856 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -85,6 +85,10 @@ def calendar_activities render 'calendar_activities', layout: false end + def exists + render json: user.id + end + private def authorize_read_user! diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index 905a8dbcd841..5ec914763fe0 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -1,30 +1,31 @@ -.login-box - - if signin_enabled? - .login-heading - %h3 New user? Create an account - - else - .login-heading - %h3 Create an account - .login-body - = form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name)) do |f| - .devise-errors - = devise_error_messages! - %div - = f.text_field :name, class: "form-control top", placeholder: "Name", required: true - %div - = f.text_field :username, class: "form-control middle", placeholder: "Username", required: true - %div - = f.email_field :email, class: "form-control middle", placeholder: "Email", required: true - .form-group.append-bottom-20#password-strength - = f.password_field :password, class: "form-control bottom", placeholder: "Password - minimum length #{@minimum_password_length} characters", required: true, pattern: ".{#{@minimum_password_length},}", title: "Minimum length is #{@minimum_password_length} characters" - %div - - if current_application_settings.recaptcha_enabled - = recaptcha_tags - %div - = f.submit "Sign up", class: "btn-create btn" +%body{ data: { page: 'signup' } } + .login-box + - if signin_enabled? + .login-heading + %h3 New user? Create an account + - else + .login-heading + %h3 Create an account + .login-body + = form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name)) do |f| + .devise-errors + = devise_error_messages! + %div + = f.text_field :name, class: "form-control top", placeholder: "Name", required: true + %div.username + = f.text_field :username, class: "form-control middle", placeholder: "Username", required: true + %div + = f.email_field :email, class: "form-control middle", placeholder: "Email", required: true + .form-group.append-bottom-20#password-strength + = f.password_field :password, class: "form-control bottom", placeholder: "Password - minimum length #{@minimum_password_length} characters", required: true, pattern: ".{#{@minimum_password_length},}", title: "Minimum length is #{@minimum_password_length} characters" + %div + - if current_application_settings.recaptcha_enabled + = recaptcha_tags + %div + = f.submit "Sign up", class: "btn-create btn" -.clearfix.prepend-top-20 - %p - %span.light Didn't receive a confirmation email? - = succeed '.' do - = link_to "Request a new one", new_confirmation_path(resource_name) + .clearfix.prepend-top-20 + %p + %span.light Didn't receive a confirmation email? + = succeed '.' do + = link_to "Request a new one", new_confirmation_path(resource_name) diff --git a/config/routes.rb b/config/routes.rb index e93b640fbc09..fde03dd07c3a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -395,6 +395,7 @@ get :projects get :contributed, as: :contributed_projects get :snippets + get :exists get '/', action: :show end -- GitLab From b8a0c57e04ec1625a002ae7ffba24cfb0bf12d01 Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Fri, 15 Jul 2016 04:21:03 +0100 Subject: [PATCH 2/9] Review changes --- app/controllers/users_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index cfee71e97856..7ce4a75ffc1b 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -86,7 +86,7 @@ def calendar_activities end def exists - render json: user.id + render json: { exists: !user.nil? } end private -- GitLab From cb6a2e534ea07fb1bf1e376abecba2fa989f0d31 Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Fri, 15 Jul 2016 04:22:01 +0100 Subject: [PATCH 3/9] Review changes --- .../javascripts/username_validator.coffee | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 app/assets/javascripts/username_validator.coffee diff --git a/app/assets/javascripts/username_validator.coffee b/app/assets/javascripts/username_validator.coffee new file mode 100644 index 000000000000..9d77fd50f8a9 --- /dev/null +++ b/app/assets/javascripts/username_validator.coffee @@ -0,0 +1,36 @@ +class @UsernameValidator + DEBOUNCE_TIMEOUT_DURATION = 1000 + ERROR_ICON_CLASSES = 'fa fa-exclamation-circle error' + USERNAME_IN_USE_MESSAGE = 'Username "$1" is in use!' + LOADING_ICON_CLASSES = 'fa fa-spinner fa-spin' + SUCCESS_ICON_CLASSES = 'fa fa-check-circle success' + TOOLTIP_PLACEMENT = 'left' + + constructor: () -> + @inputElement = $('#new_user_username') + @iconElement = $('') + @inputElement.parent().append @iconElement + + debounceTimeout = _.debounce @validateUsername, DEBOUNCE_TIMEOUT_DURATION + + @inputElement.keyup => + @iconElement.removeClass().tooltip 'destroy' + username = @inputElement.val() + return if username is '' + @iconElement.addClass LOADING_ICON_CLASSES + debounceTimeout username + + validateUsername: (username) => + $.ajax + type: 'GET' + url: "/u/#{username}/exists" + dataType: 'json' + success: (res) => + if res.exists + @iconElement.removeClass().addClass ERROR_ICON_CLASSES + .tooltip + title: USERNAME_IN_USE_MESSAGE.replace /\$1/g, username + placement: TOOLTIP_PLACEMENT + else + @iconElement.removeClass().addClass SUCCESS_ICON_CLASSES + .tooltip 'destroy' -- GitLab From a827acb281e67669f131ce95a45ceca56e0699ea Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Tue, 19 Jul 2016 22:14:42 +0100 Subject: [PATCH 4/9] altered the exists action to avoid 404ing --- app/controllers/users_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 7ce4a75ffc1b..6931a553205f 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,6 +1,6 @@ class UsersController < ApplicationController skip_before_action :authenticate_user! - before_action :user + before_action :user, except: [:exists] before_action :authorize_read_user!, only: [:show] def show @@ -86,7 +86,7 @@ def calendar_activities end def exists - render json: { exists: !user.nil? } + render json: { exists: !User.find_by_username(params[:username]).nil? } end private -- GitLab From d871c8b9a020020b210050f534e95a6a666d4d79 Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Wed, 20 Jul 2016 02:05:57 +0100 Subject: [PATCH 5/9] added feature test --- spec/features/users_spec.rb | 27 ++++++++++++++++++- .../fixtures/username_validator.html.haml | 0 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 spec/javascripts/fixtures/username_validator.html.haml diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb index b5a94fe03831..7630d299349c 100644 --- a/spec/features/users_spec.rb +++ b/spec/features/users_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Users', feature: true do +feature 'Users', feature: true, js: true do let(:user) { create(:user, username: 'user1', name: 'User 1', email: 'user1@gitlab.com') } scenario 'GET /users/sign_in creates a new user account' do @@ -40,6 +40,31 @@ expect(number_of_errors_on_page(page)).to be(1), 'errors on page:\n #{errors_on_page page}' end + feature 'username validation' do + include WaitForAjax + let(:loading_icon) { '.fa.fa-spinner' } + let(:username_input) { 'new_user_username' } + + before(:each) do + visit new_user_session_path + @username_field = find '.username' + end + + scenario 'shows an error icon if the username already exists' do + fill_in username_input, with: user.username + expect(@username_field).to have_css loading_icon + wait_for_ajax + expect(@username_field).to have_css '.fa.error' + end + + scenario 'shows a success icon if the username is available' do + fill_in username_input, with: 'new-user' + expect(@username_field).to have_css loading_icon + wait_for_ajax + expect(@username_field).to have_css '.fa.success' + end + end + def errors_on_page(page) page.find('#error_explanation').find('ul').all('li').map{ |item| item.text }.join("\n") end diff --git a/spec/javascripts/fixtures/username_validator.html.haml b/spec/javascripts/fixtures/username_validator.html.haml new file mode 100644 index 000000000000..e69de29bb2d1 -- GitLab From c176f648d9dc2c9dd64e8af3ae51b611dfba33ff Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Fri, 22 Jul 2016 16:28:46 +0100 Subject: [PATCH 6/9] Removed useless fixture --- spec/javascripts/fixtures/username_validator.html.haml | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 spec/javascripts/fixtures/username_validator.html.haml diff --git a/spec/javascripts/fixtures/username_validator.html.haml b/spec/javascripts/fixtures/username_validator.html.haml deleted file mode 100644 index e69de29bb2d1..000000000000 -- GitLab From 7911433f9c186ad8843e8d5096cdaceaf4ae7380 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 26 Jul 2016 23:30:03 +0100 Subject: [PATCH 7/9] Changed .coffee to .es6 --- .../javascripts/username_validator.coffee | 36 ------------- app/assets/javascripts/username_validator.es6 | 51 ------------------ .../javascripts/username_validator.js.es6 | 52 +++++++++++++++++++ 3 files changed, 52 insertions(+), 87 deletions(-) delete mode 100644 app/assets/javascripts/username_validator.coffee delete mode 100644 app/assets/javascripts/username_validator.es6 create mode 100644 app/assets/javascripts/username_validator.js.es6 diff --git a/app/assets/javascripts/username_validator.coffee b/app/assets/javascripts/username_validator.coffee deleted file mode 100644 index 9d77fd50f8a9..000000000000 --- a/app/assets/javascripts/username_validator.coffee +++ /dev/null @@ -1,36 +0,0 @@ -class @UsernameValidator - DEBOUNCE_TIMEOUT_DURATION = 1000 - ERROR_ICON_CLASSES = 'fa fa-exclamation-circle error' - USERNAME_IN_USE_MESSAGE = 'Username "$1" is in use!' - LOADING_ICON_CLASSES = 'fa fa-spinner fa-spin' - SUCCESS_ICON_CLASSES = 'fa fa-check-circle success' - TOOLTIP_PLACEMENT = 'left' - - constructor: () -> - @inputElement = $('#new_user_username') - @iconElement = $('') - @inputElement.parent().append @iconElement - - debounceTimeout = _.debounce @validateUsername, DEBOUNCE_TIMEOUT_DURATION - - @inputElement.keyup => - @iconElement.removeClass().tooltip 'destroy' - username = @inputElement.val() - return if username is '' - @iconElement.addClass LOADING_ICON_CLASSES - debounceTimeout username - - validateUsername: (username) => - $.ajax - type: 'GET' - url: "/u/#{username}/exists" - dataType: 'json' - success: (res) => - if res.exists - @iconElement.removeClass().addClass ERROR_ICON_CLASSES - .tooltip - title: USERNAME_IN_USE_MESSAGE.replace /\$1/g, username - placement: TOOLTIP_PLACEMENT - else - @iconElement.removeClass().addClass SUCCESS_ICON_CLASSES - .tooltip 'destroy' diff --git a/app/assets/javascripts/username_validator.es6 b/app/assets/javascripts/username_validator.es6 deleted file mode 100644 index 67dacb655cc3..000000000000 --- a/app/assets/javascripts/username_validator.es6 +++ /dev/null @@ -1,51 +0,0 @@ -((global) => { - class UsernameValidator { - constructor() { - this.debounceTimeoutDuration = 1000; - this.errorIconClasses = 'fa fa-exclamation-circle error'; - this.usernameInUseMessage = 'Username "$1" is in use!'; - this.loadingIconClasses = 'fa fa-spinner fa-spin'; - this.successIconClasses = 'fa fa-check-circle success'; - this.tooltipPlacement = 'left'; - - this.inputElement = $('#new_user_username'); - let inputContainer = this.inputElement.parent(); - inputContainer.append(''); - this.iconElement = $('i', inputContainer); - - let debounceTimeout = _.debounce(this.validateUsername, debounceTimeoutDuration); - this.inputElement.keyup(() => this.debounceRequest(debounceTimeout)); - } - - debounceRequest(debounceTimeout) { - this.iconElement.removeClass().tooltip('destroy'); - let username = this.inputElement.val(); - if (username === '') return; - this.iconElement.addClass(loadingIconClasses); - debounceTimeout(username); - } - - validateUsername(username) { - $.ajax({ - type: 'GET', - url: `/u/${username}/exists`, - dataType: 'json', - success: () => { - this.iconElement - .removeClass().addClass(errorIconClasses) - .tooltip({ - title: usernameInUseMessage.replace(/\$1/g, username), - placement: tooltipPlacement - }); - }, - error: () => { - this.iconElement - .removeClass().addClass(successIconClasses) - .tooltip('destroy') - } - }); - } - } - - global.UsernameValidator = UsernameValidator; -})(window); diff --git a/app/assets/javascripts/username_validator.js.es6 b/app/assets/javascripts/username_validator.js.es6 new file mode 100644 index 000000000000..5152eb23cceb --- /dev/null +++ b/app/assets/javascripts/username_validator.js.es6 @@ -0,0 +1,52 @@ +((global) => { + class UsernameValidator { + constructor() { + this.debounceTimeoutDuration = 1000; + this.errorIconClasses = 'fa fa-exclamation-circle error'; + this.usernameInUseMessage = 'Username "$1" is in use!'; + this.loadingIconClasses = 'fa fa-spinner fa-spin'; + this.successIconClasses = 'fa fa-check-circle success'; + this.tooltipPlacement = 'left'; + + this.inputElement = $('#new_user_username'); + this.iconElement = $(''); + this.inputElement.parent().append(this.iconElement); + + let debounceTimeout = _.debounce((username) => { + this.validateUsername(username); + }, this.debounceTimeoutDuration); + + this.inputElement.keyup(() => { + this.iconElement.removeClass().tooltip('destroy'); + let username = this.inputElement.val(); + if (username === '') return; + this.iconElement.addClass(this.loadingIconClasses); + debounceTimeout(username); + }); + } + + validateUsername(username) { + $.ajax({ + type: 'GET', + url: `/u/${username}/exists`, + dataType: 'json', + success: (res) => { + if (res.exists) { + this.iconElement + .removeClass().addClass(this.errorIconClasses) + .tooltip({ + title: this.usernameInUseMessage.replace(/\$1/g, username), + placement: this.tooltipPlacement + }); + } else { + this.iconElement + .removeClass().addClass(this.successIconClasses) + .tooltip('destroy'); + } + } + }); + } + } + + global.UsernameValidator = UsernameValidator; +})(window); -- GitLab From eab157f53219fb29e76d07c81b19c4f4e0407044 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 2 Aug 2016 20:20:16 +0100 Subject: [PATCH 8/9] changed member props to consts --- .../javascripts/username_validator.js.es6 | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/app/assets/javascripts/username_validator.js.es6 b/app/assets/javascripts/username_validator.js.es6 index 5152eb23cceb..7d096d1adf72 100644 --- a/app/assets/javascripts/username_validator.js.es6 +++ b/app/assets/javascripts/username_validator.js.es6 @@ -1,26 +1,26 @@ ((global) => { + const debounceTimeoutDuration = 1000; + const errorIconClasses = 'fa fa-exclamation-circle error'; + const usernameInUseMessage = 'Username "$1" is in use!'; + const loadingIconClasses = 'fa fa-spinner fa-spin'; + const successIconClasses = 'fa fa-check-circle success'; + const tooltipPlacement = 'left'; + class UsernameValidator { constructor() { - this.debounceTimeoutDuration = 1000; - this.errorIconClasses = 'fa fa-exclamation-circle error'; - this.usernameInUseMessage = 'Username "$1" is in use!'; - this.loadingIconClasses = 'fa fa-spinner fa-spin'; - this.successIconClasses = 'fa fa-check-circle success'; - this.tooltipPlacement = 'left'; - this.inputElement = $('#new_user_username'); this.iconElement = $(''); this.inputElement.parent().append(this.iconElement); let debounceTimeout = _.debounce((username) => { this.validateUsername(username); - }, this.debounceTimeoutDuration); + }, debounceTimeoutDuration); this.inputElement.keyup(() => { this.iconElement.removeClass().tooltip('destroy'); let username = this.inputElement.val(); if (username === '') return; - this.iconElement.addClass(this.loadingIconClasses); + this.iconElement.addClass(loadingIconClasses); debounceTimeout(username); }); } @@ -33,14 +33,14 @@ success: (res) => { if (res.exists) { this.iconElement - .removeClass().addClass(this.errorIconClasses) + .removeClass().addClass(errorIconClasses) .tooltip({ - title: this.usernameInUseMessage.replace(/\$1/g, username), - placement: this.tooltipPlacement + title: usernameInUseMessage.replace(/\$1/g, username), + placement: tooltipPlacement }); } else { this.iconElement - .removeClass().addClass(this.successIconClasses) + .removeClass().addClass(successIconClasses) .tooltip('destroy'); } } -- GitLab From 62005593f899e4a9eb253e01f3255bf647e07132 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Sat, 20 Aug 2016 13:55:07 +0100 Subject: [PATCH 9/9] Updated validation error UI to be in line with the improved login/signup page in #13937 --- .../javascripts/username_validator.js.es6 | 22 +++++-------------- app/assets/stylesheets/pages/login.scss | 10 ++++----- spec/features/users_spec.rb | 10 ++++----- 3 files changed, 14 insertions(+), 28 deletions(-) diff --git a/app/assets/javascripts/username_validator.js.es6 b/app/assets/javascripts/username_validator.js.es6 index 7d096d1adf72..9ec9764a27f0 100644 --- a/app/assets/javascripts/username_validator.js.es6 +++ b/app/assets/javascripts/username_validator.js.es6 @@ -12,37 +12,27 @@ this.iconElement = $(''); this.inputElement.parent().append(this.iconElement); - let debounceTimeout = _.debounce((username) => { + const debounceTimeout = _.debounce((username) => { this.validateUsername(username); }, debounceTimeoutDuration); this.inputElement.keyup(() => { - this.iconElement.removeClass().tooltip('destroy'); - let username = this.inputElement.val(); + this.iconElement.removeClass(); + const username = this.inputElement.val(); if (username === '') return; - this.iconElement.addClass(loadingIconClasses); debounceTimeout(username); }); } validateUsername(username) { + this.iconElement.addClass(loadingIconClasses); $.ajax({ type: 'GET', url: `/u/${username}/exists`, dataType: 'json', success: (res) => { - if (res.exists) { - this.iconElement - .removeClass().addClass(errorIconClasses) - .tooltip({ - title: usernameInUseMessage.replace(/\$1/g, username), - placement: tooltipPlacement - }); - } else { - this.iconElement - .removeClass().addClass(successIconClasses) - .tooltip('destroy'); - } + this.iconElement.removeClass(); + if (res.exists) this.inputElement.addClass('validation-error'); } }); } diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index 042b17b865a4..f997d415ff9f 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -71,12 +71,10 @@ position: absolute; right: 8px; top: 12px; - &.success { - color: $green-normal; - } - &.error { - color: $red-normal; - } + } + .validation-error { + border: 1px solid $red-normal; + box-shadow: 0 0 3px $red-normal; } } } diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb index 7630d299349c..a0b24571ea42 100644 --- a/spec/features/users_spec.rb +++ b/spec/features/users_spec.rb @@ -50,18 +50,16 @@ @username_field = find '.username' end - scenario 'shows an error icon if the username already exists' do + scenario 'shows an error border if the username already exists' do fill_in username_input, with: user.username - expect(@username_field).to have_css loading_icon wait_for_ajax - expect(@username_field).to have_css '.fa.error' + expect(@username_field).to have_css '.validation-error' end - scenario 'shows a success icon if the username is available' do + scenario 'doesn\'t show an error border if the username is available' do fill_in username_input, with: 'new-user' - expect(@username_field).to have_css loading_icon wait_for_ajax - expect(@username_field).to have_css '.fa.success' + expect(@username_field).not_to have_css '.validation-error' end end -- GitLab