diff --git a/client/i18n/locales/english/translations.json b/client/i18n/locales/english/translations.json index 4261e671f6..a615c39087 100644 --- a/client/i18n/locales/english/translations.json +++ b/client/i18n/locales/english/translations.json @@ -102,6 +102,7 @@ "contains invalid characters": "Username \"{{username}}\" contains invalid characters", "is too short": "Username \"{{username}}\" is too short", "is a reserved error code": "Username \"{{username}}\" is a reserved error code", + "must be lowercase": "Username \"{{username}}\" must be lowercase", "unavailable": "Username not available", "validating": "Validating username...", "available": "Username is available", diff --git a/client/src/components/settings/Username.js b/client/src/components/settings/Username.js index a5f3280bb1..0c7e95838e 100644 --- a/client/src/components/settings/Username.js +++ b/client/src/components/settings/Username.js @@ -98,7 +98,7 @@ class UsernameSettings extends Component { handleChange(e) { e.preventDefault(); const { username, validateUsername } = this.props; - const newValue = e.target.value.toLowerCase(); + const newValue = e.target.value; return this.setState( { formValue: newValue, diff --git a/cypress/integration/settings/username-change.js b/cypress/integration/settings/username-change.js index b8ad6347bc..2db0a49ea9 100644 --- a/cypress/integration/settings/username-change.js +++ b/cypress/integration/settings/username-change.js @@ -121,7 +121,7 @@ describe('Username input field', () => { .clear({ force: true }) .type('Quincy Larson', { force: true }); - cy.contains('Username "quincy larson" contains invalid characters') + cy.contains('Username "Quincy Larson" contains invalid characters') .should('be.visible') .should('have.attr', 'role', 'alert') // We are checking for classes here to check for proper styling @@ -198,4 +198,22 @@ describe('Username input field', () => { cy.resetUsername(); }); + it('Should show warning if username includes uppercase characters', () => { + cy.get('@usernameInput') + .clear({ force: true }) + .type('QuincyLarson', { force: true }); + + cy.contains('Username "QuincyLarson" must be lowercase') + .should('be.visible') + .should('have.attr', 'role', 'alert') + .should('have.class', 'alert alert-danger'); + }); + + it('Should not be able to click the `Save` button if username includes uppercase characters', () => { + cy.get('@usernameInput') + .clear({ force: true }) + .type('QuincyLarson', { force: true }); + + cy.get('@usernameForm').contains('Save').should('be.disabled'); + }); }); diff --git a/utils/validate.js b/utils/validate.js index 0ed4af896b..d604c41ab6 100644 --- a/utils/validate.js +++ b/utils/validate.js @@ -8,15 +8,20 @@ const usernameIsHttpStatusCode = { valid: false, error: 'is a reserved error code' }; +const usernameUpperCase = { valid: false, error: 'must be lowercase' }; const isNumeric = num => !isNaN(num); const validCharsRE = /^[a-zA-Z0-9\-_+]*$/; const isHttpStatusCode = str => isNumeric(str) && parseInt(str, 10) >= 100 && parseInt(str, 10) <= 599; +const isUsernameLowercase = str => { + return str === str.toLowerCase(); +}; const isValidUsername = str => { if (!validCharsRE.test(str)) return invalidCharError; if (str.length < 3) return usernameTooShort; if (isHttpStatusCode(str)) return usernameIsHttpStatusCode; + if (!isUsernameLowercase(str)) return usernameUpperCase; return validationSuccess; }; @@ -24,8 +29,10 @@ module.exports = { isNumeric, isHttpStatusCode, isValidUsername, + isUsernameLowercase, validationSuccess, usernameTooShort, usernameIsHttpStatusCode, - invalidCharError + invalidCharError, + usernameUpperCase }; diff --git a/utils/validate.test.js b/utils/validate.test.js index a96d8f8db5..f5c84f20e8 100644 --- a/utils/validate.test.js +++ b/utils/validate.test.js @@ -5,7 +5,8 @@ const { usernameTooShort, validationSuccess, usernameIsHttpStatusCode, - invalidCharError + invalidCharError, + usernameUpperCase } = require('./validate'); function inRange(num, range) { @@ -38,6 +39,9 @@ describe('isValidUsername', () => { expect(isValidUsername('a-b')).toStrictEqual(validationSuccess); expect(isValidUsername('a_b')).toStrictEqual(validationSuccess); }); + it('rejects uppercase characters', () => { + expect(isValidUsername('Quincy')).toStrictEqual(usernameUpperCase); + }); it('rejects all other ASCII characters', () => { const allowedCharactersList = ['-', '_', '+']; @@ -52,7 +56,7 @@ describe('isValidUsername', () => { let expected = invalidCharError; if (allowedCharactersList.includes(char)) expected = validationSuccess; if (inRange(code, numbers)) expected = validationSuccess; - if (inRange(code, upperCase)) expected = validationSuccess; + if (inRange(code, upperCase)) expected = usernameUpperCase; if (inRange(code, lowerCase)) expected = validationSuccess; expect(isValidUsername(base + char)).toStrictEqual(expected); }