From d20cc59a2633b0a8ee43a240fbb93e35821eb5ef Mon Sep 17 00:00:00 2001 From: Kasia Kosturek <36547835+kocvrek@users.noreply.github.com> Date: Fri, 2 Apr 2021 22:30:49 +0200 Subject: [PATCH] getInitials: handle more cases See [#5029]][0]. [0]: https://github.com/signalapp/Signal-Desktop/pull/5029 --- ts/test-both/util/getInitials_test.ts | 62 +++++++++++++++++++++++++++ ts/util/getInitials.ts | 31 ++++++++------ 2 files changed, 81 insertions(+), 12 deletions(-) create mode 100644 ts/test-both/util/getInitials_test.ts diff --git a/ts/test-both/util/getInitials_test.ts b/ts/test-both/util/getInitials_test.ts new file mode 100644 index 000000000..d44f79f82 --- /dev/null +++ b/ts/test-both/util/getInitials_test.ts @@ -0,0 +1,62 @@ +// Copyright 2021 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import { assert } from 'chai'; + +import { getInitials } from '../../util/getInitials'; + +describe('getInitials', () => { + it('returns undefined when passed undefined', () => { + assert.isUndefined(getInitials(undefined)); + }); + + it('returns undefined when passed an empty string', () => { + assert.isUndefined(getInitials('')); + }); + + it('returns undefined when passed a string with no letters', () => { + assert.isUndefined(getInitials('123 !@#$%')); + }); + + it('returns the first letter of a name that is one ASCII word', () => { + assert.strictEqual(getInitials('Foo'), 'F'); + assert.strictEqual(getInitials('Bo'), 'B'); + }); + + [ + 'Foo Bar', + 'foo bar', + 'F Bar', + 'Foo B', + 'FB', + 'F.B.', + '0Foo 1Bar', + "Foo B'Ar", + 'Foo Q Bar', + 'Foo Q. Bar', + 'Foo Qux Bar', + 'Foo "Qux" Bar', + 'Foo-Qux Bar', + 'Foo Bar-Qux', + "Foo b'Arr", + ].forEach(name => { + it(`returns 'FB' for '${name}'`, () => { + assert.strictEqual(getInitials(name), 'FB'); + }); + }); + + it('returns initials for languages with non-Latin alphabets', () => { + assert.strictEqual(getInitials('Иван Иванов'), 'ИИ'); + assert.strictEqual(getInitials('山田 太郎'), '山太'); + assert.strictEqual(getInitials('王五'), '王五'); + }); + + it('returns initials for right-to-left languages', () => { + assert.strictEqual(getInitials('فلانة الفلانية'), 'فا'); + assert.strictEqual(getInitials('ישראלה ישראלי'), 'יי'); + }); + + it('returns initials with diacritical marks', () => { + assert.strictEqual(getInitials('Ḟoo Ḅar'), 'ḞḄ'); + }); +}); diff --git a/ts/util/getInitials.ts b/ts/util/getInitials.ts index 856603083..dca212b1e 100644 --- a/ts/util/getInitials.ts +++ b/ts/util/getInitials.ts @@ -1,24 +1,31 @@ // Copyright 2018-2020 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -const BAD_CHARACTERS = /[^A-Za-z\s]+/g; -const WHITESPACE = /\s+/g; - -function removeNonInitials(name: string) { - return name.replace(BAD_CHARACTERS, '').replace(WHITESPACE, ' '); -} - export function getInitials(name?: string): string | undefined { if (!name) { return undefined; } - const cleaned = removeNonInitials(name); - const parts = cleaned.split(' '); - const initials = parts.map(part => part.trim()[0]); - if (!initials.length) { + const parsedName = name + // remove all chars that are not letters or separators + .replace(/[^\p{L}\p{Z}]+/gu, '') + // replace all chars that are separators with a single ASCII space + .replace(/\p{Z}+/gu, ' ') + .trim(); + + if (!parsedName) { return undefined; } - return initials.slice(0, 2).join(''); + // check if chars in the parsed string are initials + if (parsedName.length === 2 && parsedName === parsedName.toUpperCase()) { + return parsedName; + } + + const parts = parsedName.toUpperCase().split(' '); + const partsLen = parts.length; + + return partsLen === 1 + ? parts[0].charAt(0) + : parts[0].charAt(0) + parts[partsLen - 1].charAt(0); }