diff --git a/ts/components/ProfileEditor.tsx b/ts/components/ProfileEditor.tsx index dea2d82cd..8af8ebdf3 100644 --- a/ts/components/ProfileEditor.tsx +++ b/ts/components/ProfileEditor.tsx @@ -35,6 +35,7 @@ import { } from './conversation/conversation-details/ConversationDetailsIcon'; import { Spinner } from './Spinner'; import { UsernameSaveState } from '../state/ducks/conversationsEnums'; +import { MAX_USERNAME, MIN_USERNAME } from '../types/Username'; export enum EditState { None = 'None', @@ -123,26 +124,24 @@ function getUsernameInvalidKey( return undefined; } - const min = 3; - if (username.length < min) { + if (username.length < MIN_USERNAME) { return { key: 'ProfileEditor--username--check-character-min', - replacements: { min }, + replacements: { min: MIN_USERNAME }, }; } if (!/^[0-9a-z_]+$/.test(username)) { return { key: 'ProfileEditor--username--check-characters' }; } - if (/^[0-9]/.test(username)) { + if (!/^[a-z_]/.test(username)) { return { key: 'ProfileEditor--username--check-starting-character' }; } - const max = 25; - if (username.length > max) { + if (username.length > MAX_USERNAME) { return { key: 'ProfileEditor--username--check-character-max', - replacements: { max }, + replacements: { max: MAX_USERNAME }, }; } diff --git a/ts/test-node/types/Username_test.ts b/ts/test-node/types/Username_test.ts new file mode 100644 index 000000000..016161b93 --- /dev/null +++ b/ts/test-node/types/Username_test.ts @@ -0,0 +1,79 @@ +// Copyright 2021 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import { assert } from 'chai'; + +import * as Username from '../../types/Username'; + +describe('Username', () => { + describe('getUsernameFromSearch', () => { + const { getUsernameFromSearch } = Username; + + it('matches invalid username searches', () => { + assert.strictEqual(getUsernameFromSearch('username!'), 'username!'); + assert.strictEqual(getUsernameFromSearch('1username'), '1username'); + assert.strictEqual(getUsernameFromSearch('use'), 'use'); + assert.strictEqual( + getUsernameFromSearch('username9012345678901234567'), + 'username9012345678901234567' + ); + }); + + it('matches valid username searches', () => { + assert.strictEqual(getUsernameFromSearch('username_34'), 'username_34'); + assert.strictEqual(getUsernameFromSearch('u5ername'), 'u5ername'); + assert.strictEqual(getUsernameFromSearch('user'), 'user'); + assert.strictEqual( + getUsernameFromSearch('username901234567890123456'), + 'username901234567890123456' + ); + }); + + it('matches valid and invalid usernames with @ prefix', () => { + assert.strictEqual(getUsernameFromSearch('@username!'), 'username!'); + assert.strictEqual(getUsernameFromSearch('@1username'), '1username'); + assert.strictEqual(getUsernameFromSearch('@username_34'), 'username_34'); + assert.strictEqual(getUsernameFromSearch('@u5ername'), 'u5ername'); + }); + + it('matches valid and invalid usernames with @ suffix', () => { + assert.strictEqual(getUsernameFromSearch('username!@'), 'username!'); + assert.strictEqual(getUsernameFromSearch('1username@'), '1username'); + assert.strictEqual(getUsernameFromSearch('username_34@'), 'username_34'); + assert.strictEqual(getUsernameFromSearch('u5ername@'), 'u5ername'); + }); + + it('does not match something that looks like a phone number', () => { + assert.isUndefined(getUsernameFromSearch('+')); + assert.isUndefined(getUsernameFromSearch('2223')); + assert.isUndefined(getUsernameFromSearch('+3')); + assert.isUndefined(getUsernameFromSearch('+234234234233')); + }); + }); + + describe('isValidUsername', () => { + const { isValidUsername } = Username; + + it('does not match invalid username searches', () => { + assert.isFalse(isValidUsername('username!')); + assert.isFalse(isValidUsername('1username')); + assert.isFalse(isValidUsername('use')); + assert.isFalse(isValidUsername('username9012345678901234567')); + }); + + it('matches valid usernames', () => { + assert.isTrue(isValidUsername('username_34')); + assert.isTrue(isValidUsername('u5ername')); + assert.isTrue(isValidUsername('_username')); + assert.isTrue(isValidUsername('user')); + assert.isTrue(isValidUsername('username901234567890123456')); + }); + + it('does not match valid and invalid usernames with @ prefix or suffix', () => { + assert.isFalse(isValidUsername('@username_34')); + assert.isFalse(isValidUsername('@1username')); + assert.isFalse(isValidUsername('username_34@')); + assert.isFalse(isValidUsername('1username@')); + }); + }); +}); diff --git a/ts/types/Username.ts b/ts/types/Username.ts new file mode 100644 index 000000000..d4699fe5d --- /dev/null +++ b/ts/types/Username.ts @@ -0,0 +1,23 @@ +// Copyright 2021 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +export const MAX_USERNAME = 26; +export const MIN_USERNAME = 4; + +export function isValidUsername(searchTerm: string): boolean { + return /^[a-z_][0-9a-z_]{3,25}$/.test(searchTerm); +} + +export function getUsernameFromSearch(searchTerm: string): string | undefined { + if (/^[+0-9]+$/.test(searchTerm)) { + return undefined; + } + + const match = /^@?(.*?)@?$/.exec(searchTerm); + + if (match && match[1]) { + return match[1]; + } + + return undefined; +}