This is an automated email from the ASF dual-hosted git repository.

jialiang pushed a commit to branch frontend-refactor
in repository https://gitbox.apache.org/repos/asf/ambari.git


The following commit(s) were added to refs/heads/frontend-refactor by this push:
     new 52fb8cec61 AMBARI-26168: Ambari Admin React Implementation: Edit Users 
Page (#3881)
52fb8cec61 is described below

commit 52fb8cec612901baa4d0b3a383bdeccae4543931
Author: Himanshu Maurya <[email protected]>
AuthorDate: Tue Nov 19 06:32:43 2024 +0530

    AMBARI-26168: Ambari Admin React Implementation: Edit Users Page (#3881)
---
 .../ambari-admin/src/__mocks__/mockGroupNames.ts   |  64 ++
 .../ui/ambari-admin/src/__mocks__/mockUserData.ts  | 110 ++++
 .../src/screens/Users/ChangePasswordModal.tsx      | 197 ++++++
 .../ui/ambari-admin/src/screens/Users/EditUser.tsx | 721 +++++++++++++++++++++
 .../ui/ambari-admin/src/tests/EditUser.test.tsx    | 342 ++++++++++
 5 files changed, 1434 insertions(+)

diff --git 
a/ambari-admin/src/main/resources/ui/ambari-admin/src/__mocks__/mockGroupNames.ts
 
b/ambari-admin/src/main/resources/ui/ambari-admin/src/__mocks__/mockGroupNames.ts
new file mode 100644
index 0000000000..27edb2b49a
--- /dev/null
+++ 
b/ambari-admin/src/main/resources/ui/ambari-admin/src/__mocks__/mockGroupNames.ts
@@ -0,0 +1,64 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export const groupNames = {
+    "href": 
"http://host.example.com:8080/api/v1/groups?Groups/group_name.matches(.*.*)",
+    "items": [
+        {
+            "href": "http://host.example.com:8080/api/v1/groups/ferfew";,
+            "Groups": {
+                "group_name": "ferfew"
+            }
+        },
+        {
+            "href": "http://host.example.com:8080/api/v1/groups/gdgdeg";,
+            "Groups": {
+                "group_name": "gdgdeg"
+            }
+        },
+        {
+            "href": "http://host.example.com:8080/api/v1/groups/group1";,
+            "Groups": {
+                "group_name": "group1"
+            }
+        },
+        {
+            "href": "http://host.example.com:8080/api/v1/groups/group2";,
+            "Groups": {
+                "group_name": "group2"
+            }
+        },
+        {
+            "href": "http://host.example.com:8080/api/v1/groups/group3";,
+            "Groups": {
+                "group_name": "group3"
+            }
+        },
+        {
+            "href": "http://host.example.com:8080/api/v1/groups/group4";,
+            "Groups": {
+                "group_name": "group4"
+            }
+        },
+        {
+            "href": "http://host.example.com:8080/api/v1/groups/group5";,
+            "Groups": {
+                "group_name": "group5"
+            }
+        },
+    ]
+}
\ No newline at end of file
diff --git 
a/ambari-admin/src/main/resources/ui/ambari-admin/src/__mocks__/mockUserData.ts 
b/ambari-admin/src/main/resources/ui/ambari-admin/src/__mocks__/mockUserData.ts
new file mode 100644
index 0000000000..a0e63c639d
--- /dev/null
+++ 
b/ambari-admin/src/main/resources/ui/ambari-admin/src/__mocks__/mockUserData.ts
@@ -0,0 +1,110 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export const userData = {
+    "href": 
"http://host.example.com:8080/api/v1/users/dsasd?fields=privileges/PrivilegeInfo,Users";,
+    "Users": {
+        "active": true,
+        "admin": false,
+        "consecutive_failures": 0,
+        "created": 1724325120429,
+        "display_name": "dsasd",
+        "groups": [
+            "gdgdeg",
+            "group1"
+        ],
+        "ldap_user": false,
+        "local_user_name": "dsasd",
+        "user_name": "dsasd",
+        "user_type": "LOCAL"
+    },
+    "privileges": [
+        {
+            "href": 
"http://host.example.com:8080/api/v1/users/dsasd/privileges/1005";,
+            "PrivilegeInfo": {
+                "instance_name": "jukende",
+                "permission_label": "View User",
+                "permission_name": "VIEW.USER",
+                "principal_name": "dsasd",
+                "principal_type": "USER",
+                "privilege_id": 1005,
+                "type": "VIEW",
+                "user_name": "dsasd",
+                "version": "1.0.0",
+                "view_name": "FILES"
+            }
+        },
+        {
+            "href": 
"http://host.example.com:8080/api/v1/users/dsasd/privileges/1017";,
+            "PrivilegeInfo": {
+                "instance_name": "Files",
+                "permission_label": "View User",
+                "permission_name": "VIEW.USER",
+                "principal_name": "group1",
+                "principal_type": "GROUP",
+                "privilege_id": 1017,
+                "type": "VIEW",
+                "user_name": "dsasd",
+                "version": "1.0.0",
+                "view_name": "CAPACITY-SCHEDULER"
+            }
+        },
+        {
+            "href": 
"http://host.example.com:8080/api/v1/users/dsasd/privileges/1019";,
+            "PrivilegeInfo": {
+                "cluster_name": "testCluster",
+                "permission_label": "Cluster User",
+                "permission_name": "CLUSTER.USER",
+                "principal_name": "dsasd",
+                "principal_type": "USER",
+                "privilege_id": 1019,
+                "type": "CLUSTER",
+                "user_name": "dsasd"
+            }
+        },
+        {
+            "href": 
"http://host.example.com:8080/api/v1/users/dsasd/privileges/1020";,
+            "PrivilegeInfo": {
+                "instance_name": 
"2345678123456781234567812345678123456781212345678123456",
+                "permission_label": "View User",
+                "permission_name": "VIEW.USER",
+                "principal_name": "dsasd",
+                "principal_type": "USER",
+                "privilege_id": 1020,
+                "type": "VIEW",
+                "user_name": "dsasd",
+                "version": "1.0.0",
+                "view_name": "CAPACITY-SCHEDULER"
+            }
+        },
+        {
+            "href": 
"http://host.example.com:8080/api/v1/users/dsasd/privileges/1021";,
+            "PrivilegeInfo": {
+                "instance_name": "cdsadcwe@",
+                "permission_label": "View User",
+                "permission_name": "VIEW.USER",
+                "principal_name": "dsasd",
+                "principal_type": "USER",
+                "privilege_id": 1021,
+                "type": "VIEW",
+                "user_name": "dsasd",
+                "version": "1.0.0",
+                "view_name": "FILES"
+            }
+        }
+    ]
+}
\ No newline at end of file
diff --git 
a/ambari-admin/src/main/resources/ui/ambari-admin/src/screens/Users/ChangePasswordModal.tsx
 
b/ambari-admin/src/main/resources/ui/ambari-admin/src/screens/Users/ChangePasswordModal.tsx
new file mode 100644
index 0000000000..e2403d0d0e
--- /dev/null
+++ 
b/ambari-admin/src/main/resources/ui/ambari-admin/src/screens/Users/ChangePasswordModal.tsx
@@ -0,0 +1,197 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { Alert, Button, Form, Modal } from "react-bootstrap";
+import DefaultButton from "../../components/DefaultButton";
+import { useEffect, useState } from "react";
+import { get, set } from "lodash";
+
+type ChangePasswordModalProps = {
+  isOpen: boolean;
+  onClose: () => void;
+  userName: string;
+  updatePassword: (yourPassword: string, newUserPassword: string) => void;
+};
+
+export default function ChangePasswordModal({
+  isOpen,
+  onClose,
+  userName,
+  updatePassword,
+}: ChangePasswordModalProps) {
+  const [yourPassword, setYourPassword] = useState("");
+  const [newUserPassword, setNewUserPassword] = useState("");
+  const [newUserPasswordConfirmation, setNewUserPasswordConfirmation] =
+    useState("");
+  const [validationError, setValidationError] = useState({
+    yourPassword: "",
+    newUserPassword: "",
+  });
+
+  useEffect(() => {
+    if (newUserPassword !== newUserPasswordConfirmation) {
+      setValidationError({
+        ...validationError,
+        newUserPassword: "Password must match!",
+      });
+    } else {
+      setValidationError({
+        ...validationError,
+        newUserPassword: "",
+      });
+    }
+  }, [newUserPassword, newUserPasswordConfirmation]);
+
+  useEffect(() => {
+    if (!isOpen) {
+      setYourPassword("");
+      setNewUserPassword("");
+      setNewUserPasswordConfirmation("");
+      setValidationError({
+        yourPassword: "",
+        newUserPassword: "",
+      });
+    }
+  }, [isOpen]);
+
+  const handlePasswordChangeModalSave = async (event: any) => {
+    event.preventDefault();
+    let error = {
+      ...validationError
+    };
+    if (!yourPassword) {
+      set(error, "yourPassword", "Password required!");
+    }
+    if (!newUserPassword) {
+      set(error, "newUserPassword", "Password required!");
+    }
+    setValidationError(error);
+    if (
+      yourPassword &&
+      newUserPassword &&
+      newUserPasswordConfirmation &&
+      !get(error, "yourPassword") &&
+      !get(error, "newUserPassword")
+    ) {
+      updatePassword(yourPassword, newUserPassword);
+    }
+  };
+
+  return (
+    <Modal
+      show={isOpen}
+      onHide={onClose}
+      size="lg"
+      className="custom-modal-container"
+      data-testid="change-password-modal"
+    >
+      <Modal.Header>
+        <Modal.Title><h3>Change Password for {userName}</h3></Modal.Title>
+      </Modal.Header>
+      <Form onSubmit={handlePasswordChangeModalSave}>
+        <Modal.Body>
+          <Form.Group className="mb-4 d-flex">
+            <div className="w-25 d-flex justify-content-end ms-5 me-4 mt-2">
+              <Form.Label
+              className={
+                get(validationError, "yourPassword", "")
+                  ? "text-danger"
+                  : ""
+              }
+              >Your Password</Form.Label>
+            </div>
+            <div className="w-100">
+              <Form.Control
+                type="password"
+                value={yourPassword}
+                placeholder="Your Password"
+                className={
+                  get(validationError, "yourPassword", "")
+                    ? "custom-form-control border-danger"
+                    : "custom-form-control"
+                }
+                onChange={(e) => {
+                  setYourPassword(e.target.value);
+                  if (e.target.value) {
+                    setValidationError({
+                      ...validationError,
+                      yourPassword: "",
+                    });
+                  }
+                }}
+                data-testid="your-password-input"
+              />
+              {get(validationError, "yourPassword", "") ? (
+                <Alert className="mt-2 mb-0 p-2 rounded-0 text-danger" 
variant="danger">
+                  {get(validationError, "yourPassword")}
+                </Alert>
+              ) : null}
+            </div>
+          </Form.Group>
+          <Form.Group className="mb-3 d-flex">
+            <div className="w-25 d-flex justify-content-end ms-5 me-4 mt-2">
+              <Form.Label
+              className={
+                get(validationError, "newUserPassword", "")
+                  ? "text-danger"
+                  : ""
+              }
+              >New User Password</Form.Label>
+            </div>
+            <div className="w-100">
+              <Form.Control
+                type="password"
+                value={newUserPassword}
+                placeholder="New User Password"
+                className={
+                  get(validationError, "newUserPassword", "")
+                    ? "custom-form-control mb-2 border-danger"
+                    : "custom-form-control mb-2"
+                }
+                onChange={(e) => setNewUserPassword(e.target.value)}
+                data-testid="new-password-input"
+              />
+              <Form.Control
+                type="password"
+                value={newUserPasswordConfirmation}
+                placeholder="New User Password Confirmation"
+                className={
+                  get(validationError, "newUserPassword", "")
+                    ? "custom-form-control border-danger"
+                    : "custom-form-control"
+                }
+                onChange={(e) => 
setNewUserPasswordConfirmation(e.target.value)}
+                data-testid="new-confirm-password-input"
+              />
+              {get(validationError, "newUserPassword", "") ? (
+                <Alert className="mt-2 mb-0 p-2 rounded-0 text-danger" 
variant="danger">
+                  {get(validationError, "newUserPassword")}
+                </Alert>
+              ) : null}
+            </div>
+          </Form.Group>
+        </Modal.Body>
+        <Modal.Footer className="d-flex justify-content-end">
+          <DefaultButton onClick={onClose}>CANCEL</DefaultButton>
+          <Button type="submit" className="custom-btn" variant="success" 
data-testid="save-password-btn">
+            OK
+          </Button>
+        </Modal.Footer>
+      </Form>
+    </Modal>
+  );
+}
diff --git 
a/ambari-admin/src/main/resources/ui/ambari-admin/src/screens/Users/EditUser.tsx
 
b/ambari-admin/src/main/resources/ui/ambari-admin/src/screens/Users/EditUser.tsx
new file mode 100644
index 0000000000..b165623e0f
--- /dev/null
+++ 
b/ambari-admin/src/main/resources/ui/ambari-admin/src/screens/Users/EditUser.tsx
@@ -0,0 +1,721 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { useContext, useEffect, useRef, useState } from "react";
+import { Alert, Button, Form, OverlayTrigger, Tooltip } from "react-bootstrap";
+import { useParams } from "react-router";
+import { Link, useHistory, Prompt } from "react-router-dom";
+import DefaultButton from "../../components/DefaultButton";
+import RoleBasedAccessControl from "./RoleBasedAccessControl";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import {
+  faCheck,
+  faCloud,
+  faQuestionCircle,
+  faTh,
+  faTrashCan,
+} from "@fortawesome/free-solid-svg-icons";
+import Select from "react-select";
+import { get, startCase } from "lodash";
+import { permissionLabelToName, userAccessOptions } from "./constants";
+import ChangePasswordModal from "./ChangePasswordModal";
+import {
+  UserInfoType,
+  GroupNamesType,
+  PermissionLabel,
+  SelectOptionType,
+} from "./types";
+import { PrivilegeType, PrincipalType, PermissionNameType, DefaultAccess } 
from "./enums";
+import ConfirmationModal from "../../components/ConfirmationModal";
+import UserGroupApi from "../../api/userGroupApi";
+import Spinner from "../../components/Spinner";
+import { PrivilegesDataType } from "../../api/types";
+import PrivilegeApi from "../../api/privilegeApi";
+import toast from "react-hot-toast";
+import AppContent from "../../context/AppContext";
+import Table from "../../components/Table";
+import WarningModal from "./WarningModal";
+import {
+  decryptData,
+  getFromLocalStorage,
+  parseJSONData,
+} from "../../api/Utility";
+
+type ParamsType = {
+  userName: string;
+};
+
+export const constructLinkToEditInstance = (
+  viewName: string,
+  version: string,
+  instanceName: string
+) => {
+  return 
`/views/${viewName}/versions/${version}/instances/${instanceName}/edit`;
+};
+
+export default function EditUser() {
+  const params: ParamsType = useParams();
+
+  const [currentLoggedInUser, setCurrentLoggedInUser] = useState("");
+  const [showUserAccessModal, setShowUserAccessModal] = useState(false);
+  const [showChangeStatusModal, setShowChangeStatusModal] = useState(false);
+  const [showChangeAdminPriviligesModal, setShowChangeAdminPriviligesModal] =
+    useState(false);
+  const [showChangePasswordModal, setShowChangePasswordModal] = 
useState(false);
+
+  const [loading, setLoading] = useState(false);
+  const [userInfo, setUserInfo] = useState<UserInfoType | null>(null);
+  const [groupNames, setGroupNames] = useState<GroupNamesType | null>(null);
+  const [userGroups, setUserGroups] = useState<SelectOptionType[]>([]);
+  const {
+    cluster: { cluster_name: clusterName },
+    setSelectedOption,
+  } = useContext(AppContent);
+  const [showUnsavedChangesWarning, setShowUnsavedChangesWarning] =
+    useState(false);
+  const [hasUnsavedChanges, setHasUnsavedChanges] = useState(true);
+  const [isNavigating, setIsNavigating] = useState(false);
+  const [nextLocation, setNextLocation] = useState();
+  const history = useHistory();
+
+  const previousMembers = useRef<string[]>([]);
+  const newMembers = useRef<string[]>([]);
+
+  useEffect(() => {
+    setSelectedOption("Users");
+    async function getGroupNames() {
+      setLoading(true);
+      const data: any = await UserGroupApi.groupNames();
+      setGroupNames(data);
+      setLoading(false);
+    }
+    getGroupNames();
+    getUserData();
+    let ambariKey = getFromLocalStorage("ambari");
+    if (ambariKey) {
+      let parsedAmbariKey = parseJSONData(decryptData(ambariKey));
+      setCurrentLoggedInUser(get(parsedAmbariKey, "app.loginName", ""));
+    }
+  }, []);
+
+  useEffect(() => {
+    previousMembers.current = get(userInfo, "Users.groups", [] as string[]);
+  }, [userInfo]);
+
+  useEffect(() => {
+    newMembers.current = userGroups.map(
+      (group: SelectOptionType) => group.value
+    );
+    setHasUnsavedChanges(
+      JSON.stringify(previousMembers.current) !==
+        JSON.stringify(newMembers.current)
+    );
+  }, [userGroups]);
+
+  const groupOptions = get(groupNames, "items", []).map((group: any) => ({
+    value: get(group, "Groups.group_name"),
+    label: get(group, "Groups.group_name"),
+  }));
+
+  const columnsInUserPrivilegesCluster = [
+    {
+      header: "Cluster",
+      width: "80%",
+      cell: (info: any) => {
+        return (
+          <div>
+            <FontAwesomeIcon
+              icon={faCloud}
+              height={13}
+              width={13}
+              className="me-1"
+            />
+            <Link to={"/clusterInformation"} className="custom-link">
+              {get(info, "row.original.PrivilegeInfo.cluster_name")}
+            </Link>
+          </div>
+        );
+      },
+    },
+    {
+      header: "Cluster Role",
+      width: "20%",
+      cell: (info: any) => {
+        return (
+          <div>{get(info, "row.original.PrivilegeInfo.permission_label")}</div>
+        );
+      },
+    },
+  ];
+
+  const columnsInUserPrivilegesView = [
+    {
+      header: "View",
+      width: "80%",
+      cell: (info: any) => {
+        return (
+          <div className="w-100">
+            <FontAwesomeIcon
+              icon={faTh}
+              height={13}
+              width={13}
+              className="me-1"
+            />
+            <Link
+              to={constructLinkToEditInstance(
+                get(info, "row.original.PrivilegeInfo.view_name"),
+                get(info, "row.original.PrivilegeInfo.version"),
+                get(info, "row.original.PrivilegeInfo.instance_name")
+              )}
+              className="custom-link"
+            >
+              {get(info, "row.original.PrivilegeInfo.instance_name")}
+            </Link>
+          </div>
+        );
+      },
+    },
+    {
+      header: "View Permissions",
+      cell: (info: any) => {
+        return (
+          <div className="d-flex justify-content-between">
+            <div className="d-flex align-items-center">
+              {startCase(
+                get(
+                  info,
+                  "row.original.PrivilegeInfo.permission_label",
+                  ""
+                ).toLowerCase()
+              )}
+            </div>
+            <Button
+              className="btn-wrapping-icon make-all-grey"
+              onClick={() =>
+                deleteViewPrivilege(
+                  get(info, "row.original.PrivilegeInfo.view_name"),
+                  get(info, "row.original.PrivilegeInfo.version"),
+                  get(info, "row.original.PrivilegeInfo.instance_name"),
+                  get(info, "row.original.PrivilegeInfo.privilege_id")
+                )
+              }
+            >
+              <FontAwesomeIcon
+                icon={faTrashCan}
+                data-testid={`remove-privilege-icon-${get(
+                  info,
+                  "row.original.PrivilegeInfo.instance_name"
+                )}`}
+              />
+            </Button>
+          </div>
+        );
+      },
+    },
+  ];
+
+  async function getUserData() {
+    setLoading(true);
+    const data: any = await UserGroupApi.userData(
+      params.userName,
+      "privileges/PrivilegeInfo,Users"
+    );
+    setUserInfo(data);
+    setUserGroups(
+      get(data, "Users.groups", []).map((group: any) => ({
+        value: group,
+        label: group,
+      })) as SelectOptionType[]
+    );
+    setLoading(false);
+  }
+
+  const updateUserData = async (key: string, value?: boolean) => {
+    if (key === "groups") {
+      const membersToAdd: string[] = newMembers.current.filter(
+        (member) => !previousMembers.current.includes(member)
+      );
+      const membersToRemove: string[] = previousMembers.current.filter(
+        (member) => !newMembers.current.includes(member)
+      );
+      const addMembersPromises = membersToAdd.map((member) =>
+        UserGroupApi.addMember(member, get(userInfo, "Users.user_name", ""))
+      );
+      const removeMembersPromises = membersToRemove.map((member) =>
+        UserGroupApi.removeMember(member, get(userInfo, "Users.user_name", ""))
+      );
+      await Promise.all([...addMembersPromises, ...removeMembersPromises]);
+      toast.success(
+        <div className="toast-message">
+          Local Group Membership updated for{" "}
+          {get(userInfo, "Users.user_name", "")}
+        </div>
+      );
+    } else {
+      const userData = {
+        ["Users/" + key]: value,
+      };
+      await UserGroupApi.updateUser(
+        get(userInfo, "Users.user_name", ""),
+        userData
+      );
+      toast.success(
+        <div className="toast-message">
+          The {key} status for {get(userInfo, "Users.user_name", "")} is
+          updated.
+        </div>
+      );
+    }
+    getUserData();
+  };
+
+  const updatePassword = async (
+    yourPassword: string,
+    newUserPassword: string
+  ) => {
+    const userPasswordData = {
+      "Users/old_password": yourPassword,
+      "Users/password": newUserPassword,
+    };
+    await UserGroupApi.updateUser(
+      get(userInfo, "Users.user_name", ""),
+      userPasswordData
+    );
+    toast.success(
+      <div className="toast-message">
+        Password changed for {get(userInfo, "Users.user_name", "")}
+      </div>
+    );
+    setShowChangePasswordModal(false);
+  };
+
+  const updateUserDataPrivileges = async (value: PermissionLabel) => {
+    if (value === DefaultAccess.NONE) {
+      const currentClusterPrivileges = get(userInfo, "privileges", []).filter(
+        (privilige) =>
+          get(privilige, "PrivilegeInfo.type") === PrivilegeType.CLUSTER &&
+          get(privilige, "PrivilegeInfo.principal_type") === PrincipalType.USER
+      );
+      const removePrivilegesPromises = currentClusterPrivileges.map(
+        (privilege: any) =>
+          PrivilegeApi.removeClusterPrivileges(
+            clusterName,
+            get(privilege, "PrivilegeInfo.privilege_id")
+          )
+      );
+      Promise.all(removePrivilegesPromises).then(() => {
+        toast.success(
+          <div className="toast-message">
+            {get(userInfo, "Users.user_name", "")}'s explicit privilege has 
been
+            changed to 'NONE'. Any privilege now seen for this user comes
+            through its Group(s).
+          </div>
+        );
+        getUserData();
+      });
+    } else {
+      const privilegesData: PrivilegesDataType[] = [
+        {
+          PrivilegeInfo: {
+            permission_name: permissionLabelToName[value],
+            principal_name: get(userInfo, "Users.user_name", ""),
+            principal_type: PrincipalType.USER,
+          },
+        },
+      ];
+      await PrivilegeApi.addClusterPrivileges(clusterName, privilegesData);
+      toast.success(
+        <div className="toast-message">
+          {get(userInfo, "Users.user_name", "")} changed to {value}
+        </div>
+      );
+      getUserData();
+    }
+  };
+
+  const deleteViewPrivilege = async (
+    view_name: string,
+    version: string,
+    instance_name: string,
+    privilege_id: string
+  ) => {
+    await PrivilegeApi.removeViewPrivileges(
+      view_name,
+      version,
+      instance_name,
+      privilege_id
+    );
+    toast.success(
+      <div className="toast-message">
+        {instance_name} view privilege is removed for{" "}
+        {get(userInfo, "Users.user_name", "")}
+      </div>
+    );
+    getUserData();
+  };
+
+  const handleBlockedNavigation = (nextLocation: any) => {
+    if (hasUnsavedChanges) {
+      setShowUnsavedChangesWarning(true);
+      setIsNavigating(true);
+      setNextLocation(nextLocation);
+      return false;
+    }
+    return true;
+  };
+
+  const handleWarningDiscard = () => {
+    if (nextLocation) {
+      setShowUnsavedChangesWarning(false);
+      setIsNavigating(false);
+      setHasUnsavedChanges(false);
+      setTimeout(() => {
+        history.push(get(nextLocation, "pathname"));
+      }, 0);
+    }
+  };
+
+  const handleWarningSave = () => {
+    updateUserData("groups");
+    setShowUnsavedChangesWarning(false);
+    setIsNavigating(false);
+  };
+
+  return (
+    <div>
+      <Prompt when={hasUnsavedChanges} message={handleBlockedNavigation} />
+      {isNavigating && showUnsavedChangesWarning ? (
+        <WarningModal
+          isOpen={showUnsavedChangesWarning}
+          onClose={() => setShowUnsavedChangesWarning(false)}
+          handleWarningDiscard={handleWarningDiscard}
+          handleWarningSave={handleWarningSave}
+        />
+      ) : null}
+      <div className="d-flex flex-wrap">
+        <Link
+          to={"/userManagement?tab=users"}
+          className="custom-link"
+          data-testid="users-list-link"
+        >
+          <h4>Users</h4>
+        </Link>
+        <h4 className="ms-2 make-all-grey">{`/ ${get(
+          userInfo,
+          "Users.user_name",
+          ""
+        )}`}</h4>
+      </div>
+      <hr className="mb-4" />
+      {loading ? (
+        <Spinner />
+      ) : (
+        <Form className="d-flex flex-column">
+          <Form.Group className="d-flex mb-4">
+            <Form.Label className="width-15 mt-2">Type</Form.Label>
+            <Form.Control
+              value={startCase(
+                get(userInfo, "Users.user_type", "").toLowerCase()
+              )}
+              readOnly
+              plaintext
+              className="ps-4"
+            />
+          </Form.Group>
+          <Form.Group className="d-flex mb-4">
+            <Form.Label className="mt-2 width-15">Status</Form.Label>
+            <div className="d-flex">
+              {get(userInfo, "Users.user_name", "") === currentLoggedInUser ? (
+                <OverlayTrigger
+                  key="top"
+                  placement="top"
+                  overlay={<Tooltip>Cannot Change Status</Tooltip>}
+                >
+                  <div>
+                    <Form.Check
+                      type="switch"
+                      checked={get(userInfo, "Users.active", false)}
+                      onChange={() => setShowChangeStatusModal(true)}
+                      className="custom-form-check cursor-not-allowed"
+                      disabled={
+                        get(userInfo, "Users.user_name", "") ===
+                        currentLoggedInUser
+                      }
+                    />
+                  </div>
+                </OverlayTrigger>
+              ) : (
+                <Form.Check
+                  type="switch"
+                  checked={get(userInfo, "Users.active", false)}
+                  onChange={() => setShowChangeStatusModal(true)}
+                  className="custom-form-check"
+                  data-testid="user-status-switch"
+                />
+              )}
+              {get(userInfo, "Users.active", false) ? (
+                <span className="m-2 ps-2">Active</span>
+              ) : (
+                <span className="m-2 ps-2">Inactive</span>
+              )}
+            </div>
+            <ConfirmationModal
+              isOpen={showChangeStatusModal}
+              onClose={() => setShowChangeStatusModal(false)}
+              modalTitle={"Change Status"}
+              modalBody={`Are you sure you want to change status for user 
"${get(
+                userInfo,
+                "Users.user_name",
+                ""
+              )}
+            " to 
+            ${get(userInfo, "Users.active", false) ? "Inactive" : "Active"}
+            ?`}
+              successCallback={() => {
+                updateUserData("active", !get(userInfo, "Users.active", 
false));
+                setShowChangeStatusModal(false);
+              }}
+            />
+          </Form.Group>
+          <Form.Group className="d-flex mb-4">
+            <Form.Label className="mt-2 width-15">Ambari Admin</Form.Label>
+            <div className="d-flex">
+              {get(userInfo, "Users.user_name", "") === currentLoggedInUser ? (
+                <OverlayTrigger
+                  key="top"
+                  placement="top"
+                  overlay={<Tooltip>Cannot Change Admin</Tooltip>}
+                >
+                  <div>
+                    <Form.Check
+                      type="switch"
+                      checked={get(userInfo, "Users.admin", false)}
+                      onChange={() => setShowChangeAdminPriviligesModal(true)}
+                      className="custom-form-check cursor-not-allowed"
+                      disabled={
+                        get(userInfo, "Users.user_name", "") ===
+                        currentLoggedInUser
+                      }
+                    />
+                  </div>
+                </OverlayTrigger>
+              ) : (
+                <Form.Check
+                  type="switch"
+                  checked={get(userInfo, "Users.admin", false)}
+                  onChange={() => setShowChangeAdminPriviligesModal(true)}
+                  className="custom-form-check"
+                  data-testid="ambari-admin-switch"
+                />
+              )}
+              {get(userInfo, "Users.admin", false) ? (
+                <span className="m-2 ps-2">Yes</span>
+              ) : (
+                <span className="m-2 ps-2">No</span>
+              )}
+            </div>
+            <ConfirmationModal
+              isOpen={showChangeAdminPriviligesModal}
+              onClose={() => setShowChangeAdminPriviligesModal(false)}
+              modalTitle={"Change Admin Privilege"}
+              modalBody={`Are you sure you want to
+            ${get(userInfo, "Users.admin", false) ? "revoke" : "grant"} 
+            Admin privilege to user "${get(userInfo, "Users.user_name", 
"")}"?`}
+              successCallback={() => {
+                updateUserData("admin", !get(userInfo, "Users.admin", false));
+                setShowChangeAdminPriviligesModal(false);
+              }}
+            />
+          </Form.Group>
+          <Form.Group className="d-flex mb-4">
+            <Form.Label className="mt-2 width-15">Password</Form.Label>
+            <DefaultButton
+              onClick={() => {
+                if (
+                  get(userInfo, "Users.user_type", "").toLowerCase() === 
"local"
+                ) {
+                  setShowChangePasswordModal(true);
+                }
+              }}
+              className={
+                get(userInfo, "Users.user_type", "").toLowerCase() !== "local"
+                  ? "cursor-not-allowed opacity-50"
+                  : ""
+              }
+              data-testid="change-password-btn"
+            >
+              Change Password
+            </DefaultButton>
+            <ChangePasswordModal
+              isOpen={showChangePasswordModal}
+              onClose={() => setShowChangePasswordModal(false)}
+              userName={get(userInfo, "Users.user_name", "")}
+              updatePassword={(yourPassword, newUserPassword) => {
+                updatePassword(yourPassword, newUserPassword);
+              }}
+            />
+          </Form.Group>
+          <Form.Group className="d-flex mb-4">
+            <Form.Label className="mt-2 width-15">
+              Local Group Membership
+            </Form.Label>
+            <Select
+              isMulti
+              name="groups"
+              options={groupOptions}
+              className="basic-multi-select w-75"
+              placeholder="Add Group"
+              value={userGroups}
+              onChange={(e) => setUserGroups(e as SelectOptionType[])}
+              isDisabled={
+                get(userInfo, "Users.user_type", "").toLowerCase() !== "local"
+              }
+              aria-label="select-group"
+            ></Select>
+            <DefaultButton
+              onClick={() => {
+                if (
+                  get(userInfo, "Users.user_type", "").toLowerCase() ===
+                    "local" &&
+                  JSON.stringify(previousMembers) !== 
JSON.stringify(newMembers)
+                ) {
+                  updateUserData("groups");
+                }
+              }}
+              className={
+                get(userInfo, "Users.user_type", "").toLowerCase() !==
+                  "local" ||
+                JSON.stringify(previousMembers) === JSON.stringify(newMembers)
+                  ? "cursor-not-allowed opacity-50 ms-2"
+                  : "ms-2"
+              }
+              data-testid="save-groups-btn"
+            >
+              <FontAwesomeIcon icon={faCheck} />
+            </DefaultButton>
+          </Form.Group>
+          <Form.Group className="d-flex mb-4">
+            <Form.Label className="mt-2 width-15">
+              User Access{" "}
+              <FontAwesomeIcon
+                icon={faQuestionCircle}
+                onClick={() => setShowUserAccessModal(true)}
+                data-testid="user-access-info-icon"
+              />
+              <RoleBasedAccessControl
+                isOpen={showUserAccessModal}
+                onClose={() => setShowUserAccessModal(false)}
+              />
+            </Form.Label>
+            {get(userInfo, "Users.admin", false) ? (
+              <Form.Control
+                value={"Ambari Administrator"}
+                readOnly
+                plaintext
+                className="ps-4"
+              />
+            ) : (
+              <Form.Select
+                aria-label="Select"
+                className="w-25 custom-form-control"
+                onChange={(e) =>
+                  updateUserDataPrivileges(e.target.value as PermissionLabel)
+                }
+                value={get(
+                  get(userInfo, "privileges", []).filter(
+                    (privilige) =>
+                      get(privilige, "PrivilegeInfo.type") ===
+                      PrivilegeType.CLUSTER
+                  ),
+                  "[0].PrivilegeInfo.permission_label"
+                )}
+                data-testid="user-access-dropdown"
+              >
+                {userAccessOptions.map((option, idx) => (
+                  <option value={option} key={idx}>
+                    {option}
+                  </option>
+                ))}
+              </Form.Select>
+            )}
+          </Form.Group>
+          <Form.Group className="d-flex mb-4">
+            <Form.Label className="mt-2 width-15">Privileges</Form.Label>
+            {get(userInfo, "Users.admin", false) ? (
+              <Alert className="w-75" variant="info">
+                This user is an Ambari Admin and has all privileges.
+              </Alert>
+            ) : !get(userInfo, "privileges", []).filter(
+                (privilige) =>
+                  get(privilige, "PrivilegeInfo.type") ===
+                    PrivilegeType.CLUSTER &&
+                  get(privilige, "PrivilegeInfo.principal_type") ===
+                    PrincipalType.USER
+              )?.length &&
+              !get(userInfo, "privileges", []).filter(
+                (privilige) =>
+                  get(privilige, "PrivilegeInfo.permission_name") ===
+                  PermissionNameType.VIEW_USER
+              )?.length ? (
+              <Alert className="w-75" variant="info">
+                This user does not have any privileges.
+              </Alert>
+            ) : (
+              <div className="w-75 scrollable">
+                <Table
+                  data={get(userInfo, "privileges", []).filter(
+                    (privilige) =>
+                      get(privilige, "PrivilegeInfo.type") ===
+                        PrivilegeType.CLUSTER &&
+                      get(privilige, "PrivilegeInfo.principal_type") ===
+                        PrincipalType.USER
+                  )}
+                  columns={columnsInUserPrivilegesCluster}
+                />
+                {get(userInfo, "privileges", []).filter(
+                  (privilige) =>
+                    get(privilige, "PrivilegeInfo.type") ===
+                      PrivilegeType.CLUSTER &&
+                    get(privilige, "PrivilegeInfo.principal_type") ===
+                      PrincipalType.USER
+                )?.length ? null : (
+                  <div className="ps-2">No cluster privileges</div>
+                )}
+                <Table
+                  data={get(userInfo, "privileges", []).filter(
+                    (privilige) =>
+                      get(privilige, "PrivilegeInfo.permission_name") ===
+                      PermissionNameType.VIEW_USER
+                  )}
+                  columns={columnsInUserPrivilegesView}
+                  className="mt-5"
+                />
+                {get(userInfo, "privileges", []).filter(
+                  (privilige) =>
+                    get(privilige, "PrivilegeInfo.permission_name") ===
+                    PermissionNameType.VIEW_USER
+                )?.length ? null : (
+                  <div className="ps-2">No view privileges</div>
+                )}
+              </div>
+            )}
+          </Form.Group>
+        </Form>
+      )}
+    </div>
+  );
+}
diff --git 
a/ambari-admin/src/main/resources/ui/ambari-admin/src/tests/EditUser.test.tsx 
b/ambari-admin/src/main/resources/ui/ambari-admin/src/tests/EditUser.test.tsx
new file mode 100644
index 0000000000..259496675f
--- /dev/null
+++ 
b/ambari-admin/src/main/resources/ui/ambari-admin/src/tests/EditUser.test.tsx
@@ -0,0 +1,342 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { render, screen, fireEvent, waitFor } from "@testing-library/react";
+import EditUser from "../screens/Users/EditUser";
+import { describe, it, beforeEach, expect, vi } from "vitest";
+import "@testing-library/jest-dom/vitest";toast
+import UserGroupApi from "../api/userGroupApi";
+import { Router } from "react-router";
+import AppContent from "../context/AppContext";
+import { createMemoryHistory } from "history";
+import toast from "react-hot-toast";
+import { rbacData } from "../__mocks__/mockRbacData";
+import { groupNames } from "../__mocks__/mockGroupNames";
+import { userData } from "../__mocks__/mockUserData";
+import PrivilegeApi from "../api/privilegeApi";
+
+describe("EditUser component", () => {
+  const mockClusterName = "testCluster";
+  const mockContext = {
+    cluster: { cluster_name: mockClusterName },
+    setSelectedOption: vi.fn(),
+    selectedOption: "Users",
+    rbacData: {},
+    setRbacData: () => vi.fn(),
+    permissionLabelList: [],
+    setPermissionLabelList: vi.fn(),
+  };
+
+  let mockToastSuccessMessage = "";
+  let mockToastErrorMessage = "";
+
+  toast.success = (message) => {
+    mockToastSuccessMessage = message as string;
+    return "";
+  };
+
+  toast.error = (message) => {
+    mockToastErrorMessage = message as string;
+    return "";
+  };
+
+  beforeEach(() => {
+    vi.clearAllMocks();
+    mockToastSuccessMessage = "";
+    mockToastErrorMessage = "";
+    UserGroupApi.getPermissions = async () => rbacData;
+    UserGroupApi.groupNames = async () => groupNames;
+    UserGroupApi.userData = async () => userData;
+  });
+
+  const renderComponent = () => {
+    render(
+      <AppContent.Provider value={mockContext}>
+        <Router history={createMemoryHistory()}>
+          <EditUser />
+        </Router>
+      </AppContent.Provider>
+    );
+  };
+
+  it("renders EditUser component without crashing", async () => {
+    renderComponent();
+    expect(screen.getByText(/Users/i)).toBeInTheDocument();
+  });
+
+  it("handles loading state", async () => {
+    renderComponent();
+
+    await waitFor(() => {
+      expect(screen.getByTestId("admin-spinner")).toBeInTheDocument();
+    });
+  });
+
+  it("fetches and displays user data", async () => {
+    renderComponent();
+
+    await waitFor(() => {
+      expect(screen.getByText(/dsasd/i)).toBeInTheDocument();
+    });
+
+    expect(screen.getByText(/Local/i)).toBeInTheDocument();
+    expect(screen.getByText(/Active/i)).toBeInTheDocument();
+    expect(screen.getByText(/gdgdeg/i)).toBeInTheDocument();
+    expect(screen.getByText(/group1/i)).toBeInTheDocument();
+    expect(screen.getAllByText(/Cluster User/i)).toHaveLength(2);
+    expect(screen.getAllByText(/View User/i)).toHaveLength(4);
+    expect(screen.getByText(mockClusterName)).toBeInTheDocument();
+  });
+
+  it("toggles user status", async () => {
+    UserGroupApi.updateUser = async (userName: string) => {
+      toast.success(`The active status for ${userName} is updated`);
+      return { status: 200 };
+    };
+    renderComponent();
+
+    await waitFor(() => {
+      expect(screen.getByText(/dsasd/i)).toBeInTheDocument();
+    });
+
+    const statusSwitch = screen.getByTestId("user-status-switch");
+    expect(statusSwitch).toBeChecked();
+    fireEvent.click(statusSwitch);
+
+    await waitFor(() => {
+      screen.getByTestId("confirmation-modal");
+    });
+
+    const confirmButton = screen.getByTestId("confirm-ok-btn");
+    fireEvent.click(confirmButton);
+
+    await waitFor(() => {
+      expect(mockToastSuccessMessage).toBe(
+        "The active status for dsasd is updated"
+      );
+    });
+  });
+
+  it("toggles Ambari Admin status", async () => {
+    UserGroupApi.updateUser = async (userName: string) => {
+      toast.success(`The admin status for ${userName} is updated`);
+      return { status: 200 };
+    };
+    renderComponent();
+
+    await waitFor(() => {
+      expect(screen.getByText(/dsasd/i)).toBeInTheDocument();
+    });
+
+    const adminSwitch = screen.getByTestId("ambari-admin-switch");
+    expect(adminSwitch).not.toBeChecked();
+    fireEvent.click(adminSwitch);
+
+    await waitFor(() => {
+      screen.getByTestId("confirmation-modal");
+    });
+
+    const confirmButton = screen.getByTestId("confirm-ok-btn");
+    fireEvent.click(confirmButton);
+
+    await waitFor(() => {
+      expect(mockToastSuccessMessage).toBe(
+        "The admin status for dsasd is updated"
+      );
+    });
+  });
+
+  it("updates user password", async () => {
+    UserGroupApi.updateUser = async (userName: string) => {
+      toast.success(`Password changed for ${userName}`);
+      return { status: 200 };
+    };
+    renderComponent();
+
+    await waitFor(() => {
+      expect(screen.getByText(/dsasd/i)).toBeInTheDocument();
+    });
+
+    const changePasswordButton = screen.getByTestId("change-password-btn");
+    fireEvent.click(changePasswordButton);
+
+    await waitFor(() => {
+      screen.getByTestId("change-password-modal");
+    });
+
+    expect(screen.getByText(/Change Password for dsasd/i)).toBeInTheDocument();
+
+    const yourPasswordInput = screen.getByTestId("your-password-input");
+    const newPasswordInput = screen.getByTestId("new-password-input");
+    const newConfirmPasswordInput = screen.getByTestId(
+      "new-confirm-password-input"
+    );
+
+    fireEvent.change(yourPasswordInput, { target: { value: "oldPassword" } });
+    fireEvent.change(newPasswordInput, { target: { value: "newPassword" } });
+    fireEvent.change(newConfirmPasswordInput, {
+      target: { value: "newPassword" },
+    });
+
+    const savePasswordButton = screen.getByTestId("save-password-btn");
+    fireEvent.click(savePasswordButton);
+
+    await waitFor(() => {
+      expect(mockToastSuccessMessage).toBe("Password changed for dsasd");
+    });
+  });
+
+  it("updates local group membership", async () => {
+    UserGroupApi.addMember = async (_, userName: string) => {
+      toast.success(`Local Group Membership updated for ${userName}`);
+      return { status: 200 };
+    };
+    renderComponent();
+
+    await waitFor(() => {
+      expect(screen.getByText(/dsasd/i)).toBeInTheDocument();
+    });
+
+    const groupSelect = screen.getByLabelText("select-group");
+
+    fireEvent.mouseDown(groupSelect);
+    fireEvent.click(screen.getByText("ferfew"));
+
+    const saveButton = screen.getByTestId("save-groups-btn");
+    fireEvent.click(saveButton);
+
+    await waitFor(() => {
+      expect(mockToastSuccessMessage).toBe(
+        "Local Group Membership updated for dsasd"
+      );
+    });
+  });
+
+  it("renders user access dropdown and selects an option", async () => {
+    PrivilegeApi.addClusterPrivileges = async () => {
+      toast.success("User access updated successfully");
+      return { status: 200 };
+    };
+    renderComponent();
+
+    await waitFor(() => {
+      expect(screen.getByText(/dsasd/i)).toBeInTheDocument();
+    });
+
+    const userAccessSelect = screen.getByTestId(
+      "user-access-dropdown"
+    ) as HTMLSelectElement;
+    fireEvent.change(userAccessSelect, { target: { value: "Cluster User" } });
+
+    expect(userAccessSelect.value).toBe("Cluster User");
+    await waitFor(() => {
+      expect(mockToastSuccessMessage).toBe("User access updated successfully");
+    });
+  });
+
+  it("shows unsaved changes warning modal", async () => {
+    renderComponent();
+
+    await waitFor(() => {
+      expect(screen.getByText(/dsasd/i)).toBeInTheDocument();
+    });
+
+    const groupSelect = screen.getByLabelText("select-group");
+    
+    fireEvent.mouseDown(groupSelect);
+    fireEvent.click(screen.getByText("ferfew"));
+
+    const usersListButton = screen.getByTestId("users-list-link");
+    fireEvent.click(usersListButton);
+
+    await waitFor(() => {
+      expect(screen.getByTestId("warning-modal")).toBeInTheDocument();
+    });
+
+    const cancelButton = screen.getByText("Cancel");
+    fireEvent.click(cancelButton);
+
+    await waitFor(() => {
+      expect(screen.queryByTestId("warning-modal")).toBeNull();
+    });
+  });
+
+  it("renders RBAC modal for user access tooltip", async () => {
+    renderComponent();
+
+    await waitFor(() => {
+      expect(screen.getByText(/dsasd/i)).toBeInTheDocument();
+    });
+
+    const userAccessTooltipIcon = screen.getByTestId("user-access-info-icon");
+    fireEvent.click(userAccessTooltipIcon);
+
+    await waitFor(() => {
+      expect(
+        screen.getByTestId("role-based-access-control-modal")
+      ).toBeInTheDocument();
+    });
+  });
+
+  it("removes view privileges", async () => {
+    PrivilegeApi.removeViewPrivileges = async () => {
+      toast.success("View privileges removed successfully");
+      return { status: 200 };
+    };
+
+    renderComponent();
+
+    await waitFor(() => {
+      expect(screen.getByText(/dsasd/i)).toBeInTheDocument();
+    });
+
+    const removeViewButton = screen.getByTestId(
+      "remove-privilege-icon-jukende"
+    );
+    fireEvent.click(removeViewButton);
+
+    await waitFor(() => {
+      expect(mockToastSuccessMessage).toBe(
+        "View privileges removed successfully"
+      );
+    });
+  });
+
+  it("handles API errors gracefully", async () => {
+    UserGroupApi.userData = async () => {
+      toast.error("Failed to fetch user data");
+      return { status: 400 };
+    };
+
+    renderComponent();
+
+    await waitFor(() => {
+      expect(mockToastErrorMessage).toBe("Failed to fetch user data");
+    });
+  });
+
+  it("disables Local Group Membership by default unless changes are made and 
submitted", async () => {
+    renderComponent();
+
+    await waitFor(() => {
+      expect(screen.getByText(/dsasd/i)).toBeInTheDocument();
+    });
+
+    const saveButton = screen.getByTestId("save-groups-btn");
+    expect(saveButton).toHaveClass("cursor-not-allowed opacity-50");
+  });
+});


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to