mattermost-mobile/app/utils/user/index.test.ts

1740 lines
59 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/* eslint-disable max-lines */
import {Alert} from 'react-native';
import {Preferences} from '@constants';
import {Ringtone} from '@constants/calls';
import TestHelper from '@test/test_helper';
import {
confirmOutOfOfficeDisabled,
convertProfileAttributesToCustomAttributes,
convertToAttributesMap,
displayGroupMessageName,
displayUsername,
filterProfilesMatchingTerm,
getEmailInterval,
getEmailIntervalTexts,
getFullName,
getLastPictureUpdate,
getNotificationProps,
getSuggestionsSplitBy,
getSuggestionsSplitByMultiple,
getTimezone,
getTimezoneRegion,
getUserCustomStatus,
getUserIdFromChannelName,
getUsersByUsername,
getUserTimezone,
getUserTimezoneProps,
isBot,
isChannelAdmin,
isCustomStatusExpired,
isDeactivated,
isGuest,
isRoleInRoles,
isShared,
isSystemAdmin,
removeUserFromList,
getDisplayType,
sortCustomProfileAttributes,
} from './index';
import type {CustomProfileFieldModel} from '@database/models/server';
import type {CustomAttribute, CustomAttributeSet} from '@typings/api/custom_profile_attributes';
import type {IntlShape} from 'react-intl';
describe('displayUsername', () => {
const user = TestHelper.fakeUser({
username: 'johndoe',
first_name: 'John',
last_name: 'Doe',
nickname: 'Johnny',
});
it('should return the nickname if teammateDisplayNameSetting is DISPLAY_PREFER_NICKNAME', () => {
const result = displayUsername(user, 'en', Preferences.DISPLAY_PREFER_NICKNAME);
expect(result).toBe('Johnny');
});
it('should return the full name if teammateDisplayNameSetting is DISPLAY_PREFER_FULL_NAME', () => {
const result = displayUsername(user, 'en', Preferences.DISPLAY_PREFER_FULL_NAME);
expect(result).toBe('John Doe');
});
it('should return the username if teammateDisplayNameSetting is DISPLAY_PREFER_USERNAME', () => {
const result = displayUsername(user, 'en', Preferences.DISPLAY_PREFER_USERNAME);
expect(result).toBe('johndoe');
});
it('should return the username if no teammateDisplayNameSetting is provided', () => {
const result = displayUsername(user, 'en');
expect(result).toBe('johndoe');
});
it('should return "Someone" if user is not provided and useFallbackUsername is true', () => {
const result = displayUsername(undefined, 'en', Preferences.DISPLAY_PREFER_USERNAME, true);
expect(result).toBe('Someone');
});
it('should return an empty string if user is not provided and useFallbackUsername is false', () => {
const result = displayUsername(undefined, 'en', Preferences.DISPLAY_PREFER_USERNAME, false);
expect(result).toBe('');
});
it('should return the username if nickname and full name are not available', () => {
const userWithoutNicknameAndFullName = TestHelper.fakeUser({
username: 'johndoe',
first_name: '',
last_name: '',
nickname: '',
});
const result = displayUsername(userWithoutNicknameAndFullName, 'en', Preferences.DISPLAY_PREFER_NICKNAME);
expect(result).toBe('johndoe');
});
it('should return the username if name is empty or whitespace', () => {
const userWithEmptyName = TestHelper.fakeUser({
username: 'johndoe',
first_name: '',
last_name: '',
nickname: '',
});
const result = displayUsername(userWithEmptyName, 'en', Preferences.DISPLAY_PREFER_FULL_NAME);
expect(result).toBe('johndoe');
});
});
describe('displayGroupMessageName', () => {
const user1 = TestHelper.fakeUser({id: 'user1', username: 'john_doe', first_name: 'John', last_name: 'Doe'});
const user2 = TestHelper.fakeUser({id: 'user2', username: 'jane_doe', first_name: 'Jane', last_name: 'Doe'});
const user3 = TestHelper.fakeUser({id: 'user3', username: 'alice_smith', first_name: 'Alice', last_name: 'Smith'});
it('should return a comma-separated list of usernames', () => {
const users = [user1, user2, user3];
const result = displayGroupMessageName(users);
expect(result).toBe('alice_smith, jane_doe, john_doe');
});
it('should exclude the user with the specified ID', () => {
const users = [user1, user2, user3];
const result = displayGroupMessageName(users, undefined, undefined, 'user2');
expect(result).toBe('alice_smith, john_doe');
});
it('should use the full name if teammateDisplayNameSetting is DISPLAY_PREFER_FULL_NAME', () => {
const users = [user1, user2, user3];
const result = displayGroupMessageName(users, undefined, Preferences.DISPLAY_PREFER_FULL_NAME);
expect(result).toBe('Alice Smith, Jane Doe, John Doe');
});
it('should use the nickname if teammateDisplayNameSetting is DISPLAY_PREFER_NICKNAME', () => {
const userWithNickname = {...user1, nickname: 'Johnny'};
const users = [userWithNickname, user2, user3];
const result = displayGroupMessageName(users, undefined, Preferences.DISPLAY_PREFER_NICKNAME);
expect(result).toBe('Alice Smith, Jane Doe, Johnny');
});
it('should fallback to username if full name or nickname is not available', () => {
const userWithEmptyName = TestHelper.fakeUser({id: 'user4', username: 'empty_name', first_name: '', last_name: ''});
const users = [userWithEmptyName, user2, user3];
const result = displayGroupMessageName(users, undefined, Preferences.DISPLAY_PREFER_FULL_NAME);
expect(result).toBe('Alice Smith, empty_name, Jane Doe');
});
it('should handle empty user list', () => {
const users: UserProfile[] = [];
const result = displayGroupMessageName(users);
expect(result).toBe('');
});
});
describe('getFullName', () => {
it('should return the full name if both first and last names are provided - UserProfile', () => {
const user = TestHelper.fakeUser({first_name: 'John', last_name: 'Doe'});
const result = getFullName(user);
expect(result).toBe('John Doe');
});
it('should return the full name if both first and last names are provided - UserModel', () => {
const user = TestHelper.fakeUserModel({firstName: 'John', lastName: 'Doe'});
const result = getFullName(user);
expect(result).toBe('John Doe');
});
it('should return the first name if only the first name is provided', () => {
const user = TestHelper.fakeUser({first_name: 'John', last_name: ''});
const result = getFullName(user);
expect(result).toBe('John');
});
it('should return the last name if only the last name is provided', () => {
const user = TestHelper.fakeUser({first_name: '', last_name: 'Doe'});
const result = getFullName(user);
expect(result).toBe('Doe');
});
it('should return an empty string if neither first nor last name is provided', () => {
const user = TestHelper.fakeUser({first_name: '', last_name: ''});
const result = getFullName(user);
expect(result).toBe('');
});
});
describe('getUserIdFromChannelName', () => {
it('should return the other user ID from the channel name', () => {
const knownUserId = 'user1';
const channelName = 'user1__user2';
const result = getUserIdFromChannelName(knownUserId, channelName);
expect(result).toBe('user2');
});
it('should return the other user ID from the channel name when the known user ID is second', () => {
const knownUserId = 'user2';
const channelName = 'user1__user2';
const result = getUserIdFromChannelName(knownUserId, channelName);
expect(result).toBe('user1');
});
});
describe('isRoleInRoles', () => {
it('should return true if the role is in the roles string', () => {
const roles = 'system_admin system_user';
const role = 'system_admin';
const result = isRoleInRoles(roles, role);
expect(result).toBe(true);
});
it('should return false if the role is not in the roles string', () => {
const roles = 'system_user';
const role = 'system_admin';
const result = isRoleInRoles(roles, role);
expect(result).toBe(false);
});
});
describe('isGuest', () => {
it('should return true if the user is a guest', () => {
const roles = 'system_guest';
const result = isGuest(roles);
expect(result).toBe(true);
});
it('should return false if the user is not a guest', () => {
const roles = 'system_user';
const result = isGuest(roles);
expect(result).toBe(false);
});
});
describe('isSystemAdmin', () => {
it('should return true if the user is a system admin', () => {
const roles = 'system_admin';
const result = isSystemAdmin(roles);
expect(result).toBe(true);
});
it('should return false if the user is not a system admin', () => {
const roles = 'system_user';
const result = isSystemAdmin(roles);
expect(result).toBe(false);
});
});
describe('isChannelAdmin', () => {
it('should return true if the user is a channel admin', () => {
const roles = 'channel_admin';
const result = isChannelAdmin(roles);
expect(result).toBe(true);
});
it('should return false if the user is not a channel admin', () => {
const roles = 'system_user';
const result = isChannelAdmin(roles);
expect(result).toBe(false);
});
});
describe('getUsersByUsername', () => {
it('should return a dictionary of users by username', () => {
const users = [
TestHelper.fakeUserModel({username: 'john_doe'}),
TestHelper.fakeUserModel({username: 'jane_doe'}),
];
const result = getUsersByUsername(users);
expect(result).toEqual({
john_doe: users[0],
jane_doe: users[1],
});
});
});
describe('getUserTimezoneProps', () => {
it('should return the user timezone props if they exist', () => {
const user = TestHelper.fakeUserModel({timezone: {useAutomaticTimezone: 'true', automaticTimezone: 'America/New_York', manualTimezone: 'America/Los_Angeles'}});
const result = getUserTimezoneProps(user);
expect(result).toEqual({
useAutomaticTimezone: true,
automaticTimezone: 'America/New_York',
manualTimezone: 'America/Los_Angeles',
});
});
it('should return default timezone props if they do not exist', () => {
const user = TestHelper.fakeUserModel({timezone: null});
const result = getUserTimezoneProps(user);
expect(result).toEqual({
useAutomaticTimezone: true,
automaticTimezone: '',
manualTimezone: '',
});
});
});
describe('getUserTimezone', () => {
it('should return the user timezone', () => {
const user = TestHelper.fakeUserModel({timezone: {useAutomaticTimezone: 'true', automaticTimezone: 'America/New_York', manualTimezone: 'America/Los_Angeles'}});
const result = getUserTimezone(user);
expect(result).toBe('America/New_York');
});
it('should return an empty string if the user timezone does not exist', () => {
const user = TestHelper.fakeUserModel({timezone: null});
const result = getUserTimezone(user);
expect(result).toBe('');
});
});
describe('getTimezone', () => {
it('should return the automatic timezone if useAutomaticTimezone is true', () => {
const timezone: UserTimezone = {useAutomaticTimezone: 'true', automaticTimezone: 'America/New_York', manualTimezone: 'America/Los_Angeles'};
const result = getTimezone(timezone);
expect(result).toBe('America/New_York');
});
it('should return the manual timezone if useAutomaticTimezone is false', () => {
const timezone: UserTimezone = {useAutomaticTimezone: 'false', automaticTimezone: 'America/New_York', manualTimezone: 'America/Los_Angeles'};
const result = getTimezone(timezone);
expect(result).toBe('America/Los_Angeles');
});
it('should return an empty string if the timezone does not exist', () => {
const result = getTimezone(null);
expect(result).toBe('');
});
});
describe('getTimezoneRegion', () => {
it('should return the region of the timezone', () => {
const timezone = 'America/New_York';
const result = getTimezoneRegion(timezone);
expect(result).toBe('New York');
});
it('should return the timezone if it does not contain a region', () => {
const timezone = 'UTC';
const result = getTimezoneRegion(timezone);
expect(result).toBe('UTC');
});
});
describe('getUserCustomStatus', () => {
it('should return the custom status if it exists', () => {
const user = TestHelper.fakeUser({
username: 'johndoe',
props: {customStatus: '{"emoji": "smile", "text": "Happy", "duration": "today", "expires_at": "2023-12-31T23:59:59Z"}'},
});
const result = getUserCustomStatus(user);
expect(result).toEqual({
emoji: 'smile',
text: 'Happy',
duration: 'today',
expires_at: '2023-12-31T23:59:59Z',
});
});
it('should return undefined if the custom status does not exist', () => {
const user = TestHelper.fakeUserModel();
const result = getUserCustomStatus(user);
expect(result).toBeUndefined();
});
});
describe('isCustomStatusExpired', () => {
it('should return true if the custom status is expired', () => {
const user = TestHelper.fakeUser({
username: 'john_doe',
props: {customStatus: '{"emoji": "smile", "text": "Happy", "duration": "today", "expires_at": "2020-12-31T23:59:59Z"}'},
});
const result = isCustomStatusExpired(user);
expect(result).toBe(true);
});
it('should return false if the custom status is not expired', () => {
const user = TestHelper.fakeUser({
username: 'john_doe',
props: {customStatus: '{"emoji": "smile", "text": "Happy", "duration": "today", "expires_at": "2099-12-31T23:59:59Z"}'},
});
const result = isCustomStatusExpired(user);
expect(result).toBe(false);
});
it('should return true if the custom status does not exist', () => {
const user = TestHelper.fakeUserModel();
const result = isCustomStatusExpired(user);
expect(result).toBe(true);
});
});
describe('isBot', () => {
it('should return true if the user is a bot', () => {
const user = TestHelper.fakeUserModel({isBot: true});
const result = isBot(user);
expect(result).toBe(true);
});
it('should return false if the user is not a bot', () => {
const user = TestHelper.fakeUserModel({isBot: false});
const result = isBot(user);
expect(result).toBe(false);
});
});
describe('isShared', () => {
it('should return true if the user is shared', () => {
const user = TestHelper.fakeUserModel({remoteId: 'remote_id'});
const result = isShared(user);
expect(result).toBe(true);
});
it('should return false if the user is not shared', () => {
const user = TestHelper.fakeUserModel({remoteId: ''});
const result = isShared(user);
expect(result).toBe(false);
});
});
describe('getSuggestionsSplitBy', () => {
it('should split the term by the given split string and return suggestions', () => {
const term = 'one.two.three';
const splitStr = '.';
const result = getSuggestionsSplitBy(term, splitStr);
expect(result).toEqual(['one.two.three', '.two.three', 'two.three', '.three', 'three']);
});
it('should handle splitting by space', () => {
const term = 'one two three';
const splitStr = ' ';
const result = getSuggestionsSplitBy(term, splitStr);
expect(result).toEqual(['one two three', 'two three', 'three']);
});
});
describe('getSuggestionsSplitByMultiple', () => {
it('should split the term by multiple split strings and return suggestions', () => {
const term = 'one.two three';
const splitStrs = ['.', ' '];
const result = getSuggestionsSplitByMultiple(term, splitStrs);
const expected = ['.two three', 'three', 'one.two three', 'two three'].sort();
expect(result.sort()).toEqual(expected);
});
});
describe('filterProfilesMatchingTerm', () => {
const users = [
TestHelper.fakeUser({username: 'john_doe', first_name: 'John', last_name: 'Doe', nickname: 'Johnny', email: 'john@example.com'}),
TestHelper.fakeUser({username: 'jane_doe', first_name: 'Jane', last_name: 'Doe', nickname: 'Janey', email: 'jane@example.com'}),
];
it('should filter users matching the term', () => {
const term = 'john';
const result = filterProfilesMatchingTerm(users, term);
expect(result).toEqual([users[0]]);
});
it('should filter users matching the term with @ prefix', () => {
const term = '@john';
const result = filterProfilesMatchingTerm(users, term);
expect(result).toEqual([users[0]]);
});
it('should filter users matching the term in email', () => {
const term = 'example.com';
const result = filterProfilesMatchingTerm(users, term);
expect(result).toEqual(users);
});
it('should return an empty array if no users match the term', () => {
const term = 'nonexistent';
const result = filterProfilesMatchingTerm(users, term);
expect(result).toEqual([]);
});
});
describe('getNotificationProps', () => {
it('should return the user notification props if they exist', () => {
const user = TestHelper.fakeUserModel({notifyProps: TestHelper.fakeUserNotifyProps({channel: 'false', comments: 'never'})});
const result = getNotificationProps(user);
expect(result).toEqual(user.notifyProps);
});
it('should return default notification props if they do not exist', () => {
const user = TestHelper.fakeUserModel({firstName: '', notifyProps: null});
const result = getNotificationProps(user);
expect(result).toEqual({
channel: 'true',
comments: 'any',
desktop: 'all',
desktop_sound: 'true',
email: 'true',
first_name: 'false',
mark_unread: 'all',
mention_keys: `${user.username},@${user.username}`,
highlight_keys: '',
push: 'mention',
push_status: 'online',
push_threads: 'all',
email_threads: 'all',
calls_desktop_sound: 'true',
calls_notification_sound: Ringtone.Calm,
calls_mobile_sound: '',
calls_mobile_notification_sound: '',
});
});
});
describe('getEmailInterval', () => {
it('should return INTERVAL_NEVER if email notifications are disabled', () => {
const result = getEmailInterval(false, true, Preferences.INTERVAL_IMMEDIATE);
expect(result).toBe(Preferences.INTERVAL_NEVER);
});
it('should return INTERVAL_FIFTEEN_MINUTES if email batching is enabled and interval is invalid', () => {
const result = getEmailInterval(true, true, 999);
expect(result).toBe(Preferences.INTERVAL_FIFTEEN_MINUTES);
});
it('should return INTERVAL_IMMEDIATE if email batching is disabled and interval is invalid', () => {
const result = getEmailInterval(true, false, 999);
expect(result).toBe(Preferences.INTERVAL_IMMEDIATE);
});
it('should return the provided interval if it is valid', () => {
const result = getEmailInterval(true, true, Preferences.INTERVAL_HOUR);
expect(result).toBe(Preferences.INTERVAL_HOUR);
});
});
describe('getEmailIntervalTexts', () => {
it('should return the correct text for each interval', () => {
expect(getEmailIntervalTexts(`${Preferences.INTERVAL_FIFTEEN_MINUTES}`)).toEqual({id: 'notification_settings.email.fifteenMinutes', defaultMessage: 'Every 15 minutes'});
expect(getEmailIntervalTexts(`${Preferences.INTERVAL_HOUR}`)).toEqual({id: 'notification_settings.email.everyHour', defaultMessage: 'Every hour'});
expect(getEmailIntervalTexts(`${Preferences.INTERVAL_IMMEDIATE}`)).toEqual({id: 'notification_settings.email.immediately', defaultMessage: 'Immediately'});
expect(getEmailIntervalTexts(`${Preferences.INTERVAL_NEVER}`)).toEqual({id: 'notification_settings.email.never', defaultMessage: 'Never'});
});
});
describe('getLastPictureUpdate', () => {
it('should return bot_last_icon_update if the user is a bot', () => {
const user = TestHelper.fakeUserModel({isBot: true, props: {bot_last_icon_update: 12345}});
const result = getLastPictureUpdate(user);
expect(result).toBe(12345);
});
it('should return lastPictureUpdate if the user is not a bot', () => {
const user = TestHelper.fakeUserModel({isBot: false, lastPictureUpdate: 67890});
const result = getLastPictureUpdate(user);
expect(result).toBe(67890);
});
it('should return 0 if lastPictureUpdate is not available', () => {
const user = TestHelper.fakeUserModel({isBot: false});
const result = getLastPictureUpdate(user);
expect(result).toBe(0);
});
});
describe('isDeactivated', () => {
it('should return true if the user is deactivated', () => {
const user = TestHelper.fakeUser({delete_at: 12345});
const result = isDeactivated(user);
expect(result).toBe(true);
});
it('should return false if the user is not deactivated', () => {
const user = TestHelper.fakeUser({delete_at: 0});
const result = isDeactivated(user);
expect(result).toBe(false);
});
it('should return true if the user is deactivated using deleteAt', () => {
const user = TestHelper.fakeUserModel({deleteAt: 12345});
const result = isDeactivated(user);
expect(result).toBe(true);
});
it('should return false if the user is not deactivated using deleteAt', () => {
const user = TestHelper.fakeUserModel({deleteAt: 0});
const result = isDeactivated(user);
expect(result).toBe(false);
});
});
describe('removeUserFromList', () => {
const user1 = TestHelper.fakeUser({id: 'user1'});
const user2 = TestHelper.fakeUser({id: 'user2'});
const user3 = TestHelper.fakeUser({id: 'user3'});
it('should remove the user from the list', () => {
const originalList = [user1, user2, user3];
const result = removeUserFromList('user2', originalList);
expect(result).toEqual([user1, user3]);
});
it('should return the original list if the user is not found', () => {
const originalList = [user1, user2, user3];
const result = removeUserFromList('user4', originalList);
expect(result).toEqual(originalList);
});
it('should return an empty list if the original list is empty', () => {
const originalList: UserProfile[] = [];
const result = removeUserFromList('user1', originalList);
expect(result).toEqual([]);
});
});
describe('confirmOutOfOfficeDisabled', () => {
const intl = {
formatMessage: jest.fn(({defaultMessage}, values) => {
if (values) {
return defaultMessage.replace('{status}', values.status);
}
return defaultMessage;
}),
} as unknown as IntlShape;
const updateStatus = jest.fn();
const alert = jest.fn();
beforeEach(() => {
jest.clearAllMocks();
jest.spyOn(Alert, 'alert').mockImplementation(alert);
});
it('should show an alert with the correct messages for dnd status', () => {
confirmOutOfOfficeDisabled(intl, 'dnd', updateStatus);
expect(intl.formatMessage).toHaveBeenCalledWith({
id: 'mobile.set_status.dnd',
defaultMessage: 'Do Not Disturb',
});
expect(alert).toHaveBeenCalledWith(
'Disable "Out Of Office"?',
'Would you like to switch your status to "Do Not Disturb" and disable Automatic Replies?',
[
{
text: 'Cancel',
style: 'cancel',
},
{
text: 'OK',
onPress: expect.any(Function),
},
],
);
});
it('should show an alert with the correct messages for other statuses', () => {
confirmOutOfOfficeDisabled(intl, 'away', updateStatus);
expect(intl.formatMessage).toHaveBeenCalledWith({
id: 'mobile.set_status.away',
defaultMessage: 'Away',
});
expect(alert).toHaveBeenCalledWith(
'Disable "Out Of Office"?',
'Would you like to switch your status to "Away" and disable Automatic Replies?',
[
{
text: 'Cancel',
style: 'cancel',
},
{
text: 'OK',
onPress: expect.any(Function),
},
],
);
});
it('should call updateStatus with the correct status when OK is pressed', () => {
confirmOutOfOfficeDisabled(intl, 'online', updateStatus);
const okButton = jest.mocked(Alert.alert).mock.calls[0][2]?.[1];
okButton?.onPress?.();
expect(updateStatus).toHaveBeenCalledWith('online');
});
});
describe('convertToAttributesMap', () => {
it('should convert an array of custom attributes to a map', () => {
const attributes: CustomAttribute[] = [
{id: 'attr1', name: 'Attribute 1', type: 'text', value: 'value1', sort_order: 1},
{id: 'attr2', name: 'Attribute 2', type: 'text', value: 'value2', sort_order: 2},
];
const result = convertToAttributesMap(attributes);
expect(result).toEqual({
attr1: {id: 'attr1', name: 'Attribute 1', type: 'text', value: 'value1', sort_order: 1},
attr2: {id: 'attr2', name: 'Attribute 2', type: 'text', value: 'value2', sort_order: 2},
});
});
it('should return the input if it is already a map', () => {
const attributesMap: CustomAttributeSet = {
attr1: {id: 'attr1', name: 'Attribute 1', type: 'text', value: 'value1', sort_order: 1},
attr2: {id: 'attr2', name: 'Attribute 2', type: 'text', value: 'value2', sort_order: 2},
};
const result = convertToAttributesMap(attributesMap);
expect(result).toBe(attributesMap);
});
it('should handle empty array input', () => {
const result = convertToAttributesMap([]);
expect(result).toEqual({});
});
it('should handle array with single attribute', () => {
const attributes: CustomAttribute[] = [{id: 'attr1', name: 'Attribute 1', type: 'text', value: 'value1', sort_order: 1}];
const result = convertToAttributesMap(attributes);
expect(result).toEqual({
attr1: {id: 'attr1', name: 'Attribute 1', type: 'text', value: 'value1', sort_order: 1},
});
});
it('should handle attributes with missing optional fields', () => {
const attributes: CustomAttribute[] = [
{id: 'attr1', name: 'Attribute 1', type: 'text', value: ''},
{id: 'attr2', name: 'Attribute 2', type: 'text', value: 'value2'},
];
const result = convertToAttributesMap(attributes);
expect(result).toEqual({
attr1: {id: 'attr1', name: 'Attribute 1', type: 'text', value: ''},
attr2: {id: 'attr2', name: 'Attribute 2', type: 'text', value: 'value2'},
});
});
});
describe('convertProfileAttributesToCustomAttributes', () => {
const mockFields = [
TestHelper.fakeCustomProfileFieldModel({
id: 'field1',
name: 'Field 1',
type: 'text',
attrs: {sort_order: 1},
}),
TestHelper.fakeCustomProfileFieldModel({
id: 'field2',
name: 'Field 2',
type: 'text',
attrs: {sort_order: 2},
}),
];
const mockAttributes = [
TestHelper.fakeCustomProfileAttributeModel({
fieldId: 'field1',
value: 'value1',
}),
TestHelper.fakeCustomProfileAttributeModel({
fieldId: 'field2',
value: 'value2',
}),
];
it('should convert profile attributes to custom attributes', () => {
const result = convertProfileAttributesToCustomAttributes(mockAttributes, mockFields);
expect(result).toEqual([
{
id: 'field1',
name: 'Field 1',
type: 'text',
value: 'value1',
sort_order: 1,
},
{
id: 'field2',
name: 'Field 2',
type: 'text',
value: 'value2',
sort_order: 2,
},
]);
});
it('should handle missing fields', () => {
const attributes = [TestHelper.fakeCustomProfileAttributeModel({
fieldId: 'unknown_field',
value: 'value',
})];
const result = convertProfileAttributesToCustomAttributes(attributes, mockFields);
expect(result).toEqual([{
id: 'unknown_field',
name: 'unknown_field',
type: 'text',
value: 'value',
sort_order: Number.MAX_SAFE_INTEGER,
}]);
});
it('should handle missing sort_order in field attrs', () => {
const fields = [TestHelper.fakeCustomProfileFieldModel({
id: 'field1',
name: 'Field 1',
type: 'text',
attrs: {},
})];
const attributes = [TestHelper.fakeCustomProfileAttributeModel({
fieldId: 'field1',
value: 'value1',
})];
const result = convertProfileAttributesToCustomAttributes(attributes, fields);
expect(result).toEqual([{
id: 'field1',
name: 'Field 1',
type: 'text',
value: 'value1',
sort_order: Number.MAX_SAFE_INTEGER,
}]);
});
it('should handle empty attributes array', () => {
const result = convertProfileAttributesToCustomAttributes([], mockFields);
expect(result).toEqual([]);
});
it('should handle null attributes', () => {
const result = convertProfileAttributesToCustomAttributes(null, mockFields);
expect(result).toEqual([]);
});
it('should handle undefined attributes', () => {
const result = convertProfileAttributesToCustomAttributes(undefined, mockFields);
expect(result).toEqual([]);
});
it('should handle null fields', () => {
const result = convertProfileAttributesToCustomAttributes(mockAttributes, null);
expect(result).toEqual([
{
id: 'field1',
name: 'field1',
type: 'text',
value: 'value1',
sort_order: Number.MAX_SAFE_INTEGER,
},
{
id: 'field2',
name: 'field2',
type: 'text',
value: 'value2',
sort_order: Number.MAX_SAFE_INTEGER,
},
]);
});
it('should handle undefined fields', () => {
const result = convertProfileAttributesToCustomAttributes(mockAttributes, undefined);
expect(result).toEqual([
{
id: 'field1',
name: 'field1',
type: 'text',
value: 'value1',
sort_order: Number.MAX_SAFE_INTEGER,
},
{
id: 'field2',
name: 'field2',
type: 'text',
value: 'value2',
sort_order: Number.MAX_SAFE_INTEGER,
},
]);
});
it('should sort attributes when sort function is provided', () => {
const customSort = (a: CustomAttribute, b: CustomAttribute) => (b.sort_order ?? 0) - (a.sort_order ?? 0);
const result = convertProfileAttributesToCustomAttributes(mockAttributes, mockFields, customSort);
expect(result).toEqual([
{
id: 'field2',
name: 'Field 2',
type: 'text',
value: 'value2',
sort_order: 2,
},
{
id: 'field1',
name: 'Field 1',
type: 'text',
value: 'value1',
sort_order: 1,
},
]);
});
it('should not convert values for non-select/multiselect fields', () => {
const textField = TestHelper.fakeCustomProfileFieldModel({
id: 'text_field',
name: 'Text Field',
type: 'text',
attrs: {sort_order: 1},
});
const attributes = [TestHelper.fakeCustomProfileAttributeModel({
fieldId: 'text_field',
value: 'some text value',
})];
const result = convertProfileAttributesToCustomAttributes(attributes, [textField]);
expect(result).toEqual([{
id: 'text_field',
name: 'Text Field',
type: 'text',
value: 'some text value',
sort_order: 1,
}]);
});
// Additional tests for edge cases and robustness
it('should handle multiselect fields with JSON array values containing spaces', () => {
const multiselectField = TestHelper.fakeCustomProfileFieldModel({
id: 'multiselect_field',
name: 'Multiselect Field',
type: 'multiselect',
attrs: {
sort_order: 1,
options: [
{id: 'opt1', name: 'Option 1'},
{id: 'opt2', name: 'Option 2'},
{id: 'opt3', name: 'Option 3'},
],
},
});
const attributes = [TestHelper.fakeCustomProfileAttributeModel({
fieldId: 'multiselect_field',
value: '["opt1", "opt2", "opt3"]', // JSON with spaces
})];
const result = convertProfileAttributesToCustomAttributes(attributes, [multiselectField]);
expect(result).toEqual([{
id: 'multiselect_field',
name: 'Multiselect Field',
type: 'multiselect',
value: 'Option 1, Option 2, Option 3',
sort_order: 1,
}]);
});
it('should handle multiselect fields with mixed valid and invalid option IDs', () => {
const multiselectField = TestHelper.fakeCustomProfileFieldModel({
id: 'multiselect_field',
name: 'Multiselect Field',
type: 'multiselect',
attrs: {
sort_order: 1,
options: [
{id: 'opt1', name: 'Option 1'},
{id: 'opt2', name: 'Option 2'},
],
},
});
const attributes = [TestHelper.fakeCustomProfileAttributeModel({
fieldId: 'multiselect_field',
value: '["opt1", "invalid_opt", "opt2"]',
})];
const result = convertProfileAttributesToCustomAttributes(attributes, [multiselectField]);
expect(result).toEqual([{
id: 'multiselect_field',
name: 'Multiselect Field',
type: 'multiselect',
value: 'Option 1, invalid_opt, Option 2',
sort_order: 1,
}]);
});
it('should handle multiselect fields with empty JSON array', () => {
const multiselectField = TestHelper.fakeCustomProfileFieldModel({
id: 'multiselect_field',
name: 'Multiselect Field',
type: 'multiselect',
attrs: {
sort_order: 1,
options: [
{id: 'opt1', name: 'Option 1'},
],
},
});
const attributes = [TestHelper.fakeCustomProfileAttributeModel({
fieldId: 'multiselect_field',
value: '[]',
})];
const result = convertProfileAttributesToCustomAttributes(attributes, [multiselectField]);
expect(result).toEqual([{
id: 'multiselect_field',
name: 'Multiselect Field',
type: 'multiselect',
value: '',
sort_order: 1,
}]);
});
it('should handle multiselect fields with malformed JSON gracefully', () => {
const multiselectField = TestHelper.fakeCustomProfileFieldModel({
id: 'multiselect_field',
name: 'Multiselect Field',
type: 'multiselect',
attrs: {
sort_order: 1,
options: [
{id: 'opt1', name: 'Option 1'},
{id: 'opt2', name: 'Option 2'},
],
},
});
const attributes = [TestHelper.fakeCustomProfileAttributeModel({
fieldId: 'multiselect_field',
value: '["opt1", "opt2"', // Malformed JSON (missing closing bracket)
})];
const result = convertProfileAttributesToCustomAttributes(attributes, [multiselectField]);
// Should fallback to comma-separated parsing
expect(result).toEqual([{
id: 'multiselect_field',
name: 'Multiselect Field',
type: 'multiselect',
value: '["opt1", "opt2"', // Should return original value since it can't be parsed
sort_order: 1,
}]);
});
it('should handle select/multiselect fields with no options defined', () => {
const selectFieldNoOptions = TestHelper.fakeCustomProfileFieldModel({
id: 'select_field',
name: 'Select Field',
type: 'select',
attrs: {
sort_order: 1,
// No options defined
},
});
const attributes = [TestHelper.fakeCustomProfileAttributeModel({
fieldId: 'select_field',
value: 'some_value',
})];
const result = convertProfileAttributesToCustomAttributes(attributes, [selectFieldNoOptions]);
expect(result).toEqual([{
id: 'select_field',
name: 'Select Field',
type: 'select',
value: 'some_value', // Should return original value
sort_order: 1,
}]);
});
it('should handle multiselect fields with comma-separated values containing spaces', () => {
const multiselectField = TestHelper.fakeCustomProfileFieldModel({
id: 'multiselect_field',
name: 'Multiselect Field',
type: 'multiselect',
attrs: {
sort_order: 1,
options: [
{id: 'opt1', name: 'Option 1'},
{id: 'opt2', name: 'Option 2'},
{id: 'opt3', name: 'Option 3'},
],
},
});
const attributes = [TestHelper.fakeCustomProfileAttributeModel({
fieldId: 'multiselect_field',
value: 'opt1, opt2 , opt3', // Comma-separated with spaces
})];
const result = convertProfileAttributesToCustomAttributes(attributes, [multiselectField]);
expect(result).toEqual([{
id: 'multiselect_field',
name: 'Multiselect Field',
type: 'multiselect',
value: 'Option 1, Option 2, Option 3',
sort_order: 1,
}]);
});
// Test single select fields with option conversion
it('should handle select fields with option ID conversion', () => {
const selectField = TestHelper.fakeCustomProfileFieldModel({
id: 'select_field',
name: 'Select Field',
type: 'select',
attrs: {
sort_order: 1,
options: [
{id: 'opt1', name: 'Option 1'},
{id: 'opt2', name: 'Option 2'},
{id: 'opt3', name: 'Option 3'},
],
},
});
const attributes = [TestHelper.fakeCustomProfileAttributeModel({
fieldId: 'select_field',
value: 'opt2',
})];
const result = convertProfileAttributesToCustomAttributes(attributes, [selectField]);
expect(result).toEqual([{
id: 'select_field',
name: 'Select Field',
type: 'select',
value: 'Option 2',
sort_order: 1,
}]);
});
it('should handle select fields with invalid option ID', () => {
const selectField = TestHelper.fakeCustomProfileFieldModel({
id: 'select_field',
name: 'Select Field',
type: 'select',
attrs: {
sort_order: 1,
options: [
{id: 'opt1', name: 'Option 1'},
{id: 'opt2', name: 'Option 2'},
],
},
});
const attributes = [TestHelper.fakeCustomProfileAttributeModel({
fieldId: 'select_field',
value: 'invalid_option',
})];
const result = convertProfileAttributesToCustomAttributes(attributes, [selectField]);
expect(result).toEqual([{
id: 'select_field',
name: 'Select Field',
type: 'select',
value: 'invalid_option', // Should return original value when ID not found
sort_order: 1,
}]);
});
it('should handle select fields with empty options array', () => {
const selectField = TestHelper.fakeCustomProfileFieldModel({
id: 'select_field',
name: 'Select Field',
type: 'select',
attrs: {
sort_order: 1,
options: [],
},
});
const attributes = [TestHelper.fakeCustomProfileAttributeModel({
fieldId: 'select_field',
value: 'some_value',
})];
const result = convertProfileAttributesToCustomAttributes(attributes, [selectField]);
expect(result).toEqual([{
id: 'select_field',
name: 'Select Field',
type: 'select',
value: 'some_value', // Should return original value
sort_order: 1,
}]);
});
it('should handle select fields with null options', () => {
const selectField = TestHelper.fakeCustomProfileFieldModel({
id: 'select_field',
name: 'Select Field',
type: 'select',
attrs: {
sort_order: 1,
options: undefined,
},
});
const attributes = [TestHelper.fakeCustomProfileAttributeModel({
fieldId: 'select_field',
value: 'some_value',
})];
const result = convertProfileAttributesToCustomAttributes(attributes, [selectField]);
expect(result).toEqual([{
id: 'select_field',
name: 'Select Field',
type: 'select',
value: 'some_value', // Should return original value
sort_order: 1,
}]);
});
it('should handle options with missing id or name properties', () => {
const selectField = TestHelper.fakeCustomProfileFieldModel({
id: 'select_field',
name: 'Select Field',
type: 'select',
attrs: {
sort_order: 1,
options: [
{id: 'opt1', name: 'Option 1'},
{id: 'opt3', name: 'Option 3'},
],
},
});
const attributes = [TestHelper.fakeCustomProfileAttributeModel({
fieldId: 'select_field',
value: 'opt1',
})];
const result = convertProfileAttributesToCustomAttributes(attributes, [selectField]);
expect(result).toEqual([{
id: 'select_field',
name: 'Select Field',
type: 'select',
value: 'Option 1',
sort_order: 1,
}]);
});
// Test useDisplayType parameter
it('should use display type when useDisplayType is true', () => {
const textField = TestHelper.fakeCustomProfileFieldModel({
id: 'text_field',
name: 'Text Field',
type: 'text',
attrs: {
sort_order: 1,
value_type: 'email',
},
});
const attributes = [TestHelper.fakeCustomProfileAttributeModel({
fieldId: 'text_field',
value: 'test@example.com',
})];
const result = convertProfileAttributesToCustomAttributes(attributes, [textField], undefined, true);
expect(result).toEqual([{
id: 'text_field',
name: 'Text Field',
type: 'email',
value: 'test@example.com',
sort_order: 1,
}]);
});
it('should use field type when useDisplayType is false', () => {
const textField = TestHelper.fakeCustomProfileFieldModel({
id: 'text_field',
name: 'Text Field',
type: 'text',
attrs: {
sort_order: 1,
value_type: 'email',
},
});
const attributes = [TestHelper.fakeCustomProfileAttributeModel({
fieldId: 'text_field',
value: 'test@example.com',
})];
const result = convertProfileAttributesToCustomAttributes(attributes, [textField], undefined, false);
expect(result).toEqual([{
id: 'text_field',
name: 'Text Field',
type: 'text',
value: 'test@example.com',
sort_order: 1,
}]);
});
it('should handle empty values for select/multiselect fields', () => {
const selectField = TestHelper.fakeCustomProfileFieldModel({
id: 'select_field',
name: 'Select Field',
type: 'select',
attrs: {
sort_order: 1,
options: [
{id: 'opt1', name: 'Option 1'},
],
},
});
const attributes = [TestHelper.fakeCustomProfileAttributeModel({
fieldId: 'select_field',
value: '',
})];
const result = convertProfileAttributesToCustomAttributes(attributes, [selectField]);
expect(result).toEqual([{
id: 'select_field',
name: 'Select Field',
type: 'select',
value: '',
sort_order: 1,
}]);
});
it('should handle multiselect with single item JSON array', () => {
const multiselectField = TestHelper.fakeCustomProfileFieldModel({
id: 'multiselect_field',
name: 'Multiselect Field',
type: 'multiselect',
attrs: {
sort_order: 1,
options: [
{id: 'opt1', name: 'Option 1'},
{id: 'opt2', name: 'Option 2'},
],
},
});
const attributes = [TestHelper.fakeCustomProfileAttributeModel({
fieldId: 'multiselect_field',
value: '["opt1"]',
})];
const result = convertProfileAttributesToCustomAttributes(attributes, [multiselectField]);
expect(result).toEqual([{
id: 'multiselect_field',
name: 'Multiselect Field',
type: 'multiselect',
value: 'Option 1',
sort_order: 1,
}]);
});
it('should handle multiselect with duplicate option IDs', () => {
const multiselectField = TestHelper.fakeCustomProfileFieldModel({
id: 'multiselect_field',
name: 'Multiselect Field',
type: 'multiselect',
attrs: {
sort_order: 1,
options: [
{id: 'opt1', name: 'Option 1'},
{id: 'opt2', name: 'Option 2'},
],
},
});
const attributes = [TestHelper.fakeCustomProfileAttributeModel({
fieldId: 'multiselect_field',
value: '["opt1", "opt1", "opt2"]',
})];
const result = convertProfileAttributesToCustomAttributes(attributes, [multiselectField]);
expect(result).toEqual([{
id: 'multiselect_field',
name: 'Multiselect Field',
type: 'multiselect',
value: 'Option 1, Option 1, Option 2',
sort_order: 1,
}]);
});
it('should handle fields with no attrs property', () => {
const field = TestHelper.fakeCustomProfileFieldModel({
id: 'field1',
name: 'Field 1',
type: 'text',
});
// Remove attrs property completely
delete (field as any).attrs;
const attributes = [TestHelper.fakeCustomProfileAttributeModel({
fieldId: 'field1',
value: 'value1',
})];
const result = convertProfileAttributesToCustomAttributes(attributes, [field]);
expect(result).toEqual([{
id: 'field1',
name: 'Field 1',
type: 'text',
value: 'value1',
sort_order: Number.MAX_SAFE_INTEGER,
}]);
});
it('should handle mixed field types in same conversion', () => {
const fields = [
TestHelper.fakeCustomProfileFieldModel({
id: 'text_field',
name: 'Text Field',
type: 'text',
attrs: {sort_order: 1},
}),
TestHelper.fakeCustomProfileFieldModel({
id: 'select_field',
name: 'Select Field',
type: 'select',
attrs: {
sort_order: 2,
options: [{id: 'opt1', name: 'Option 1'}],
},
}),
TestHelper.fakeCustomProfileFieldModel({
id: 'multiselect_field',
name: 'Multiselect Field',
type: 'multiselect',
attrs: {
sort_order: 3,
options: [{id: 'opt2', name: 'Option 2'}, {id: 'opt3', name: 'Option 3'}],
},
}),
];
const attributes = [
TestHelper.fakeCustomProfileAttributeModel({
fieldId: 'text_field',
value: 'text value',
}),
TestHelper.fakeCustomProfileAttributeModel({
fieldId: 'select_field',
value: 'opt1',
}),
TestHelper.fakeCustomProfileAttributeModel({
fieldId: 'multiselect_field',
value: '["opt2", "opt3"]',
}),
];
const result = convertProfileAttributesToCustomAttributes(attributes, fields);
expect(result).toEqual([
{
id: 'text_field',
name: 'Text Field',
type: 'text',
value: 'text value',
sort_order: 1,
},
{
id: 'select_field',
name: 'Select Field',
type: 'select',
value: 'Option 1',
sort_order: 2,
},
{
id: 'multiselect_field',
name: 'Multiselect Field',
type: 'multiselect',
value: 'Option 2, Option 3',
sort_order: 3,
},
]);
});
});
describe('getDisplayType', () => {
it('should return value_type when field type is text and value_type exists', () => {
const field = {
type: 'text',
attrs: {
value_type: 'email',
},
} as CustomProfileFieldModel;
expect(getDisplayType(field)).toBe('email');
});
it('should return field type when value_type is empty string', () => {
const field = {
type: 'text',
attrs: {
value_type: '',
},
} as CustomProfileFieldModel;
expect(getDisplayType(field)).toBe('text');
});
it('should return field type when value_type is undefined', () => {
const field = {
type: 'text',
attrs: {},
} as CustomProfileFieldModel;
expect(getDisplayType(field)).toBe('text');
});
it('should return field type when field type is not text', () => {
const field = {
type: 'select',
attrs: {
value_type: 'email',
},
} as CustomProfileFieldModel;
expect(getDisplayType(field)).toBe('select');
});
it('should return field type when attrs is undefined', () => {
const field = TestHelper.fakeCustomProfileFieldModel({
type: 'text',
});
// Manually set attrs to undefined to test edge case
(field as any).attrs = undefined;
expect(getDisplayType(field)).toBe('text');
});
});
describe('sortCustomProfileAttributes', () => {
it('should sort attributes by sort_order', () => {
const attr1: CustomAttribute = {
id: 'attr1',
name: 'Attribute 1',
type: 'text',
value: 'value1',
sort_order: 2,
};
const attr2: CustomAttribute = {
id: 'attr2',
name: 'Attribute 2',
type: 'text',
value: 'value2',
sort_order: 1,
};
const result = [attr1, attr2].sort(sortCustomProfileAttributes);
expect(result).toEqual([attr2, attr1]);
});
it('should NOT convert sort_order of 0 to MAX_SAFE_INTEGER', () => {
const attr1: CustomAttribute = {
id: 'attr1',
name: 'Z Attribute',
type: 'text',
value: 'value1',
sort_order: 0,
};
const attr2: CustomAttribute = {
id: 'attr2',
name: 'A Attribute',
type: 'text',
value: 'value2',
sort_order: 1,
};
const result = [attr2, attr1].sort(sortCustomProfileAttributes);
// attr1 with sort_order 0 should come first, not last
expect(result).toEqual([attr1, attr2]);
});
it('should handle sort_order of 0 correctly when mixed with undefined', () => {
const attr1: CustomAttribute = {
id: 'attr1',
name: 'B Attribute',
type: 'text',
value: 'value1',
sort_order: 0,
};
const attr2: CustomAttribute = {
id: 'attr2',
name: 'A Attribute',
type: 'text',
value: 'value2',
// undefined sort_order should go to end
};
const attr3: CustomAttribute = {
id: 'attr3',
name: 'C Attribute',
type: 'text',
value: 'value3',
sort_order: 1,
};
const result = [attr2, attr3, attr1].sort(sortCustomProfileAttributes);
// Order should be: attr1 (0), attr3 (1), attr2 (undefined -> MAX_SAFE_INTEGER)
expect(result).toEqual([attr1, attr3, attr2]);
});
it('should sort by name when sort_order values are equal', () => {
const attr1: CustomAttribute = {
id: 'attr1',
name: 'Z Attribute',
type: 'text',
value: 'value1',
sort_order: 1,
};
const attr2: CustomAttribute = {
id: 'attr2',
name: 'A Attribute',
type: 'text',
value: 'value2',
sort_order: 1,
};
const result = [attr1, attr2].sort(sortCustomProfileAttributes);
expect(result).toEqual([attr2, attr1]);
});
it('should handle multiple attributes with sort_order 0', () => {
const attr1: CustomAttribute = {
id: 'attr1',
name: 'Z Attribute',
type: 'text',
value: 'value1',
sort_order: 0,
};
const attr2: CustomAttribute = {
id: 'attr2',
name: 'A Attribute',
type: 'text',
value: 'value2',
sort_order: 0,
};
const attr3: CustomAttribute = {
id: 'attr3',
name: 'M Attribute',
type: 'text',
value: 'value3',
sort_order: 1,
};
const result = [attr1, attr3, attr2].sort(sortCustomProfileAttributes);
// Both attr1 and attr2 have sort_order 0, so they should be sorted by name
// attr3 has sort_order 1, so it comes after
expect(result).toEqual([attr2, attr1, attr3]);
});
it('should handle negative sort_order values correctly', () => {
const attr1: CustomAttribute = {
id: 'attr1',
name: 'Attribute 1',
type: 'text',
value: 'value1',
sort_order: -1,
};
const attr2: CustomAttribute = {
id: 'attr2',
name: 'Attribute 2',
type: 'text',
value: 'value2',
sort_order: 0,
};
const attr3: CustomAttribute = {
id: 'attr3',
name: 'Attribute 3',
type: 'text',
value: 'value3',
sort_order: 1,
};
const result = [attr3, attr2, attr1].sort(sortCustomProfileAttributes);
expect(result).toEqual([attr1, attr2, attr3]);
});
it('should handle fields with sort_order 0 in convertProfileAttributesToCustomAttributes', () => {
const mockFields = [
TestHelper.fakeCustomProfileFieldModel({
id: 'field1',
name: 'First Field',
type: 'text',
attrs: {sort_order: 0}, // This should NOT become MAX_SAFE_INTEGER
}),
TestHelper.fakeCustomProfileFieldModel({
id: 'field2',
name: 'Second Field',
type: 'text',
attrs: {sort_order: 1},
}),
TestHelper.fakeCustomProfileFieldModel({
id: 'field3',
name: 'Third Field',
type: 'text',
attrs: {}, // No sort_order, should become MAX_SAFE_INTEGER
}),
];
const mockAttributes = [
TestHelper.fakeCustomProfileAttributeModel({
fieldId: 'field1',
value: 'value1',
}),
TestHelper.fakeCustomProfileAttributeModel({
fieldId: 'field2',
value: 'value2',
}),
TestHelper.fakeCustomProfileAttributeModel({
fieldId: 'field3',
value: 'value3',
}),
];
const result = convertProfileAttributesToCustomAttributes(
mockAttributes,
mockFields,
sortCustomProfileAttributes,
);
expect(result).toEqual([
{
id: 'field1',
name: 'First Field',
type: 'text',
value: 'value1',
sort_order: 0, // Should remain 0, not MAX_SAFE_INTEGER
},
{
id: 'field2',
name: 'Second Field',
type: 'text',
value: 'value2',
sort_order: 1,
},
{
id: 'field3',
name: 'Third Field',
type: 'text',
value: 'value3',
sort_order: Number.MAX_SAFE_INTEGER,
},
]);
});
});