diff --git a/app/screens/edit_profile/edit_profile.test.tsx b/app/screens/edit_profile/edit_profile.test.tsx
index e967a2efa..430234a63 100644
--- a/app/screens/edit_profile/edit_profile.test.tsx
+++ b/app/screens/edit_profile/edit_profile.test.tsx
@@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {act} from '@testing-library/react-hooks';
-import {fireEvent} from '@testing-library/react-native';
+import {fireEvent, screen, waitFor} from '@testing-library/react-native';
import React from 'react';
import AvailableScreens from '@constants/screens';
@@ -67,9 +67,19 @@ jest.mock('@context/server', () => ({
// Mock for custom profile attributes API
const mockFetchCustomProfileAttributes = jest.fn();
+// Add mock for updateCustomProfileAttributes to track calls
+const mockUpdateCustomProfileAttributes = jest.fn();
+
+// Mock logError function
+const mockLogError = jest.fn();
+
jest.mock('@actions/remote/custom_profile', () => ({
fetchCustomProfileAttributes: (...args: any[]) => mockFetchCustomProfileAttributes(...args),
- updateCustomProfileAttributes: jest.fn().mockResolvedValue({success: true, error: undefined}),
+ updateCustomProfileAttributes: (...args: any[]) => mockUpdateCustomProfileAttributes(...args),
+}));
+
+jest.mock('@utils/log', () => ({
+ logError: (...args: any[]) => mockLogError(...args),
}));
jest.mock('@actions/remote/user', () => ({
@@ -155,6 +165,12 @@ describe('EditProfile', () => {
attributes: serverAttributesSet,
error: undefined,
}));
+
+ // Reset updateCustomProfileAttributes mock
+ mockUpdateCustomProfileAttributes.mockResolvedValue({success: true, error: undefined});
+
+ // Reset logError mock
+ mockLogError.mockClear();
});
it('should update custom attribute value while preserving name and sort order', async () => {
@@ -388,4 +404,201 @@ describe('EditProfile', () => {
const newAttributeItem = await findAllByTestId('edit_profile_form.customAttributes.attr4.input');
expect(newAttributeItem[0].props.value).toBe('new db value');
});
+
+ describe('Submission Logic', () => {
+ it('should update custom attributes when enableCustomAttributes is true and customAttributes exist', async () => {
+ renderWithIntlAndTheme(
+ ,
+ );
+
+ // Wait for component to load and custom attributes to be fetched
+ await waitFor(async() => {
+ const customAttributeItems = await screen.findAllByTestId(new RegExp('^edit_profile_form.customAttributes.attr[0-9]+.input$'));
+ expect(customAttributeItems.length).toBeGreaterThan(0);
+ });
+
+ // Modify a field to trigger the hasUpdateUserInfo flag
+ await act(async () => {
+ const customAttributeItems = await screen.findAllByTestId(new RegExp('^edit_profile_form.customAttributes.attr[0-9]+.input$'));
+ fireEvent.changeText(customAttributeItems[0], 'modified value');
+ });
+
+ // Trigger form submission
+ const saveButton = screen.getByTestId('edit_profile.save.button');
+ await act(async () => {
+ fireEvent.press(saveButton);
+ });
+
+ // Verify that updateCustomProfileAttributes was called
+ expect(mockUpdateCustomProfileAttributes).toHaveBeenCalledWith(
+ 'http://localhost:8065',
+ 'user1',
+ expect.objectContaining({
+ attr1: expect.objectContaining({
+ value: 'modified value',
+ }),
+ }),
+ );
+ });
+
+ it('should not update custom attributes when enableCustomAttributes is false', async () => {
+ const {getByTestId} = renderWithIntlAndTheme(
+ ,
+ );
+
+ // Wait for component to load
+ await act(async () => {
+ await new Promise((resolve) => setTimeout(resolve, 100));
+ });
+
+ // Modify a standard field to trigger the hasUpdateUserInfo flag
+ const firstNameField = getByTestId('edit_profile_form.firstName.input');
+ await act(async () => {
+ fireEvent.changeText(firstNameField, 'Modified John');
+ });
+
+ // Trigger form submission
+ const saveButton = getByTestId('edit_profile.save.button');
+ await act(async () => {
+ fireEvent.press(saveButton);
+ });
+
+ // Verify that updateCustomProfileAttributes was NOT called
+ expect(mockUpdateCustomProfileAttributes).not.toHaveBeenCalled();
+ });
+
+ it('should call updateCustomProfileAttributes with empty object when customAttributes is empty', async () => {
+ // Mock server fetch to return empty attributes for this test
+ mockFetchCustomProfileAttributes.mockResolvedValue({
+ attributes: {},
+ error: undefined,
+ });
+
+ const {getByTestId} = renderWithIntlAndTheme(
+ ,
+ );
+
+ // Wait for component to load
+ await act(async () => {
+ await new Promise((resolve) => setTimeout(resolve, 100));
+ });
+
+ // Modify a standard field to trigger the hasUpdateUserInfo flag
+ const firstNameField = getByTestId('edit_profile_form.firstName.input');
+ await act(async () => {
+ fireEvent.changeText(firstNameField, 'Modified John');
+ });
+
+ // Trigger form submission
+ const saveButton = getByTestId('edit_profile.save.button');
+ await act(async () => {
+ fireEvent.press(saveButton);
+ });
+
+ // Verify that updateCustomProfileAttributes was called with empty object
+ expect(mockUpdateCustomProfileAttributes).toHaveBeenCalledWith(
+ 'http://localhost:8065',
+ 'user1',
+ {},
+ );
+ });
+
+ it('should handle custom attributes update error gracefully', async () => {
+ // Mock updateCustomProfileAttributes to return an error
+ mockUpdateCustomProfileAttributes.mockResolvedValue({
+ success: false,
+ error: new Error('Failed to update custom attributes'),
+ });
+
+ renderWithIntlAndTheme(
+ ,
+ );
+
+ // Wait for component to load and custom attributes to be fetched
+ await act(async () => {
+ await new Promise((resolve) => setTimeout(resolve, 100));
+ });
+
+ // Modify a custom attribute to trigger the hasUpdateUserInfo flag
+ const customAttributeItems = await screen.findAllByTestId(new RegExp('^edit_profile_form.customAttributes.attr[0-9]+.input$'));
+ await act(async () => {
+ fireEvent.changeText(customAttributeItems[0], 'modified value that will error');
+ });
+
+ // Trigger form submission
+ const saveButton = screen.getByTestId('edit_profile.save.button');
+ await act(async () => {
+ fireEvent.press(saveButton);
+ });
+
+ // Verify that updateCustomProfileAttributes was called
+ expect(mockUpdateCustomProfileAttributes).toHaveBeenCalled();
+
+ // Verify error handling - check that error is logged
+ await waitFor(() => {
+ expect(mockLogError).toHaveBeenCalledWith(
+ 'Error updating custom attributes',
+ expect.objectContaining({
+ message: 'Failed to update custom attributes',
+ }),
+ );
+ });
+ });
+ });
});
diff --git a/app/screens/edit_profile/edit_profile.tsx b/app/screens/edit_profile/edit_profile.tsx
index 16924f58a..36b79c011 100644
--- a/app/screens/edit_profile/edit_profile.tsx
+++ b/app/screens/edit_profile/edit_profile.tsx
@@ -19,6 +19,7 @@ import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
import useNavButtonPressed from '@hooks/navigation_button_pressed';
import SecurityManager from '@managers/security_manager';
import {dismissModal, popTopScreen, setButtons} from '@screens/navigation';
+import {logError} from '@utils/log';
import {preventDoubleTap} from '@utils/tap';
import ProfileForm, {CUSTOM_ATTRS_PREFIX} from './components/form';
@@ -186,10 +187,11 @@ const EditProfile = ({
}
// Update custom attributes if changed
- if (userInfo.customAttributes) {
+ if (userInfo.customAttributes && enableCustomAttributes) {
const {error: attrError} = await updateCustomProfileAttributes(serverUrl, currentUser.id, userInfo.customAttributes);
if (attrError) {
- resetScreen(attrError);
+ logError('Error updating custom attributes', attrError);
+ resetScreenForProfileError(attrError);
return;
}
}