mattermost-mobile/app/screens/edit_profile/components/form.test.tsx
Guillermo Vayá 7891079a61
[MM-64358] don't save unneeded custom profile attributes (#8971)
* send only needed changes, take saml into account

* adhere to naming conventions

* fix tests

* address review comments
2025-07-02 17:49:55 +02:00

632 lines
21 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {fireEvent, screen} from '@testing-library/react-native';
import React, {type ComponentProps} from 'react';
import {renderWithIntl} from '@test/intl-test-helper';
import TestHelper from '@test/test_helper';
import ProfileForm from './form';
import type {CustomAttributeSet} from '@typings/api/custom_profile_attributes';
// Mock AutocompleteSelector to avoid database dependency
jest.mock('@components/autocomplete_selector', () => ({
__esModule: true,
default: jest.fn(),
}));
const MockAutocompleteSelector = jest.requireMock('@components/autocomplete_selector').default;
MockAutocompleteSelector.mockImplementation((props: any) =>
React.createElement('AutocompleteSelector', {...props}),
);
describe('ProfileForm', () => {
const baseProps: ComponentProps<typeof ProfileForm> = {
canSave: false,
currentUser: TestHelper.fakeUserModel({
id: 'user1',
firstName: 'First',
lastName: 'Last',
username: 'username',
email: 'test@test.com',
nickname: 'nick',
position: 'position',
authService: '',
}),
isTablet: false,
lockedFirstName: false,
lockedLastName: false,
lockedNickname: false,
lockedPosition: false,
onUpdateField: jest.fn(),
submitUser: jest.fn(),
userInfo: {
firstName: 'First',
lastName: 'Last',
username: 'username',
email: 'test@test.com',
nickname: 'nick',
position: 'position',
customAttributes: {},
},
enableCustomAttributes: false,
};
it('should render without custom attributes when disabled', () => {
const {queryByTestId} = renderWithIntl(
<ProfileForm {...baseProps}/>,
);
expect(queryByTestId('edit_profile_form.nickname')).toBeTruthy();
expect(queryByTestId('edit_profile_form.customAttributes.field1')).toBeNull();
});
it('should render custom attributes when enabled', () => {
const props = {
...baseProps,
enableCustomAttributes: true,
userInfo: {
...baseProps.userInfo,
customAttributes: {
field1: {
id: 'field1',
name: 'Field 1',
type: 'text',
value: 'value1',
},
field2: {
id: 'field2',
name: 'Field 2',
type: 'text',
value: 'value2',
},
},
},
};
const {getByTestId} = renderWithIntl(
<ProfileForm {...props}/>,
);
expect(getByTestId('edit_profile_form.nickname')).toBeTruthy();
expect(getByTestId('edit_profile_form.customAttributes.field1')).toBeTruthy();
expect(getByTestId('edit_profile_form.customAttributes.field2')).toBeTruthy();
});
it('should call onUpdateField when custom attribute is changed', () => {
const onUpdateField = jest.fn();
const props = {
...baseProps,
enableCustomAttributes: true,
onUpdateField,
userInfo: {
...baseProps.userInfo,
customAttributes: {
field1: {
id: 'field1',
name: 'Field 1',
type: 'text',
value: 'value1',
},
},
},
};
const {getByTestId} = renderWithIntl(
<ProfileForm {...props}/>,
);
const input = getByTestId('edit_profile_form.customAttributes.field1.input');
fireEvent.changeText(input, 'new value');
expect(onUpdateField).toHaveBeenCalledWith('customAttributes.field1', 'new value');
});
it('should handle empty custom attributes', () => {
const props = {
...baseProps,
enableCustomAttributes: true,
userInfo: {
...baseProps.userInfo,
customAttributes: {},
},
};
const {queryByTestId} = renderWithIntl(
<ProfileForm {...props}/>,
);
expect(queryByTestId('edit_profile_form.customAttributes.field1')).toBeNull();
});
test('should maintain custom attributes sort order', () => {
const customAttributes: CustomAttributeSet = {
attr1: {
id: 'attr1',
name: 'Department',
value: 'Engineering',
sort_order: 1,
type: 'text',
},
attr2: {
id: 'attr2',
name: 'Location',
value: 'Remote',
sort_order: 0,
type: 'text',
},
attr3: {
id: 'attr3',
name: 'Start Date',
value: '2023',
sort_order: 2,
type: 'text',
},
};
const props = {
...baseProps,
enableCustomAttributes: true,
userInfo: {
...baseProps.userInfo,
customAttributes,
},
};
const {getAllByTestId} = renderWithIntl(
<ProfileForm
{...props}
/>,
);
const attributeFields = getAllByTestId(/^edit_profile_form.customAttributes\.attr\d$/);
// Verify fields are rendered in sort order
expect(attributeFields[0].props.testID).toBe('edit_profile_form.customAttributes.attr2'); // sort_order: 0
expect(attributeFields[1].props.testID).toBe('edit_profile_form.customAttributes.attr1'); // sort_order: 1
expect(attributeFields[2].props.testID).toBe('edit_profile_form.customAttributes.attr3'); // sort_order: 2
});
it('should render SelectField for select type custom attributes', () => {
const customFields = [
TestHelper.fakeCustomProfileFieldModel({
id: 'department',
name: 'Department',
type: 'select',
attrs: {
options: [
{id: 'eng', name: 'Engineering'},
{id: 'mkt', name: 'Marketing'},
{id: 'sales', name: 'Sales'},
],
},
}),
];
const props = {
...baseProps,
enableCustomAttributes: true,
customFields,
userInfo: {
...baseProps.userInfo,
customAttributes: {
department: {
id: 'department',
name: 'Department',
type: 'select',
value: 'eng',
},
},
},
};
renderWithIntl(
<ProfileForm {...props}/>,
);
expect(screen.getByTestId('edit_profile_form.customAttributes.department')).toBeTruthy();
});
it('should render SelectField for multiselect type custom attributes', () => {
const customFields = [
TestHelper.fakeCustomProfileFieldModel({
id: 'skills',
name: 'Skills',
type: 'multiselect',
attrs: {
options: [
{id: 'js', name: 'JavaScript'},
{id: 'react', name: 'React'},
{id: 'ts', name: 'TypeScript'},
],
},
}),
];
const props = {
...baseProps,
enableCustomAttributes: true,
customFields,
userInfo: {
...baseProps.userInfo,
customAttributes: {
skills: {
id: 'skills',
name: 'Skills',
type: 'multiselect',
value: 'js,react',
},
},
},
};
renderWithIntl(
<ProfileForm {...props}/>,
);
expect(screen.getByTestId('edit_profile_form.customAttributes.skills')).toBeTruthy();
});
it('should render SelectField for select type custom attributes with correct value', () => {
const customFields = [
TestHelper.fakeCustomProfileFieldModel({
id: 'department',
name: 'Department',
type: 'select',
attrs: {
options: [
{id: 'eng', name: 'Engineering'},
{id: 'mkt', name: 'Marketing'},
],
},
}),
];
const props = {
...baseProps,
enableCustomAttributes: true,
customFields,
userInfo: {
...baseProps.userInfo,
customAttributes: {
department: {
id: 'department',
name: 'Department',
type: 'select',
value: 'eng',
},
},
},
};
renderWithIntl(
<ProfileForm {...props}/>,
);
// Verify that the SelectField is rendered with the correct testID
expect(screen.getByTestId('edit_profile_form.customAttributes.department')).toBeTruthy();
});
it('should render SelectField for multiselect type custom attributes with correct value', () => {
const customFields = [
TestHelper.fakeCustomProfileFieldModel({
id: 'skills',
name: 'Skills',
type: 'multiselect',
attrs: {
options: [
{id: 'js', name: 'JavaScript'},
{id: 'react', name: 'React'},
{id: 'ts', name: 'TypeScript'},
],
},
}),
];
const props = {
...baseProps,
enableCustomAttributes: true,
customFields,
userInfo: {
...baseProps.userInfo,
customAttributes: {
skills: {
id: 'skills',
name: 'Skills',
type: 'multiselect',
value: 'js',
},
},
},
};
renderWithIntl(
<ProfileForm {...props}/>,
);
// Verify that the SelectField is rendered with the correct testID
expect(screen.getByTestId('edit_profile_form.customAttributes.skills')).toBeTruthy();
});
it('should render text field for custom attributes without field definition', () => {
const props = {
...baseProps,
enableCustomAttributes: true,
customFields: [], // No field definitions
userInfo: {
...baseProps.userInfo,
customAttributes: {
unknownField: {
id: 'unknownField',
name: 'Unknown Field',
type: 'text',
value: 'some value',
},
},
},
};
renderWithIntl(
<ProfileForm {...props}/>,
);
// Should render as text field since no field definition exists
expect(screen.getByTestId('edit_profile_form.customAttributes.unknownField')).toBeTruthy();
});
it('should render text field for custom attributes with unsupported type', () => {
const customFields = [
TestHelper.fakeCustomProfileFieldModel({
id: 'customField',
name: 'Custom Field',
type: 'unsupported_type',
attrs: {},
}),
];
const props = {
...baseProps,
enableCustomAttributes: true,
customFields,
userInfo: {
...baseProps.userInfo,
customAttributes: {
customField: {
id: 'customField',
name: 'Custom Field',
type: 'unsupported_type',
value: 'some value',
},
},
},
};
renderWithIntl(
<ProfileForm {...props}/>,
);
// Should render as text field for unsupported types
expect(screen.getByTestId('edit_profile_form.customAttributes.customField')).toBeTruthy();
});
it('should render ProfileForm without errors when custom fields have no field definitions', () => {
const props = {
...baseProps,
enableCustomAttributes: true,
customFields: [], // No field definitions
userInfo: {
...baseProps.userInfo,
customAttributes: {
unknownField: {
id: 'unknownField',
name: 'Unknown Field',
type: 'text',
value: 'some value',
},
},
},
};
const {getByTestId} = renderWithIntl(
<ProfileForm {...props}/>,
);
// Should render as a text field since no field definition exists
expect(getByTestId('edit_profile_form.customAttributes.unknownField')).toBeTruthy();
});
describe('SAML Field Disabling', () => {
it('should disable custom fields when SAML-linked', () => {
const customFields = [
TestHelper.fakeCustomProfileFieldModel({
id: 'saml-field',
name: 'SAML Field',
type: 'text',
attrs: {
saml: 'Department', // SAML-linked
},
}),
TestHelper.fakeCustomProfileFieldModel({
id: 'normal-field',
name: 'Normal Field',
type: 'text',
attrs: {
saml: '', // Not SAML-linked
},
}),
];
const props = {
...baseProps,
enableCustomAttributes: true,
customFields,
userInfo: {
...baseProps.userInfo,
customAttributes: {
'saml-field': {
id: 'saml-field',
name: 'SAML Field',
type: 'text',
value: 'Engineering',
},
'normal-field': {
id: 'normal-field',
name: 'Normal Field',
type: 'text',
value: 'Mobile Team',
},
},
},
};
const {getByTestId} = renderWithIntl(
<ProfileForm {...props}/>,
);
const samlField = getByTestId('edit_profile_form.customAttributes.saml-field.input.disabled');
const normalField = getByTestId('edit_profile_form.customAttributes.normal-field.input');
// SAML field should be disabled
expect(samlField.props.editable).toBe(false);
// Normal field should be enabled
expect(normalField.props.editable).toBe(true);
});
it('should enable custom fields when SAML attribute is empty', () => {
const customFields = [
TestHelper.fakeCustomProfileFieldModel({
id: 'normal-field',
name: 'Normal Field',
type: 'text',
attrs: {
saml: '', // Empty SAML attribute
},
}),
];
const props = {
...baseProps,
enableCustomAttributes: true,
customFields,
userInfo: {
...baseProps.userInfo,
customAttributes: {
'normal-field': {
id: 'normal-field',
name: 'Normal Field',
type: 'text',
value: 'Some value',
},
},
},
};
const {getByTestId} = renderWithIntl(
<ProfileForm {...props}/>,
);
const normalField = getByTestId('edit_profile_form.customAttributes.normal-field.input');
expect(normalField.props.editable).toBe(true);
});
it('should enable custom fields when SAML attribute is missing', () => {
const customFields = [
TestHelper.fakeCustomProfileFieldModel({
id: 'normal-field',
name: 'Normal Field',
type: 'text',
attrs: {
// No saml attribute at all
},
}),
];
const props = {
...baseProps,
enableCustomAttributes: true,
customFields,
userInfo: {
...baseProps.userInfo,
customAttributes: {
'normal-field': {
id: 'normal-field',
name: 'Normal Field',
type: 'text',
value: 'Some value',
},
},
},
};
const {getByTestId} = renderWithIntl(
<ProfileForm {...props}/>,
);
const normalField = getByTestId('edit_profile_form.customAttributes.normal-field.input');
expect(normalField.props.editable).toBe(true);
});
it('should handle custom fields without field definitions (defaults to enabled)', () => {
const props = {
...baseProps,
enableCustomAttributes: true,
customFields: [], // No field definitions
userInfo: {
...baseProps.userInfo,
customAttributes: {
'unknown-field': {
id: 'unknown-field',
name: 'Unknown Field',
type: 'text',
value: 'Some value',
},
},
},
};
const {getByTestId} = renderWithIntl(
<ProfileForm {...props}/>,
);
const unknownField = getByTestId('edit_profile_form.customAttributes.unknown-field.input');
// Should default to enabled when no field definition exists
expect(unknownField.props.editable).toBe(true);
});
it('should disable standard profile fields when SAML/LDAP locked', () => {
const props = {
...baseProps,
currentUser: TestHelper.fakeUserModel({
...baseProps.currentUser,
authService: 'saml',
}),
lockedFirstName: true,
lockedLastName: false,
lockedNickname: true,
lockedPosition: false,
};
const {getByTestId} = renderWithIntl(
<ProfileForm {...props}/>,
);
const firstNameField = getByTestId('edit_profile_form.firstName.input.disabled');
const lastNameField = getByTestId('edit_profile_form.lastName.input');
const nicknameField = getByTestId('edit_profile_form.nickname.input.disabled');
const positionField = getByTestId('edit_profile_form.position.input');
// Locked fields should be disabled
expect(firstNameField.props.editable).toBe(false);
expect(nicknameField.props.editable).toBe(false);
// Unlocked fields should be enabled
expect(lastNameField.props.editable).toBe(true);
expect(positionField.props.editable).toBe(true);
});
});
});