Skip to content

Commit

Permalink
UserSettings disable save button (#2488)
Browse files Browse the repository at this point in the history
* Fix buggy disabled save button

* Update hasAvatar when onSubmit is called

* Update user in parent rather than props in child

* Add argument to render UserSettings test

* Specify setUser function and stop double submit

* Create UserSettings test for disabled save button

* Modify tests (#2508)

Co-authored-by: Danny Rorabaugh <[email protected]>

---------

Co-authored-by: Danny Rorabaugh <[email protected]>
  • Loading branch information
Apoktieno and imnasnainaec authored Aug 25, 2023
1 parent 136bb89 commit 18a7210
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 7 deletions.
1 change: 1 addition & 0 deletions src/components/UserSettings/AvatarUpload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export default function AvatarUpload(props: { doneCallback?: () => void }) {

async function upload(e: React.FormEvent<EventTarget>) {
e.preventDefault();
e.stopPropagation();
if (file) {
setLoading(true);
await uploadAvatar(getUserId(), file)
Expand Down
20 changes: 15 additions & 5 deletions src/components/UserSettings/UserSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,20 @@ export enum UserSettingsIds {
SelectUiLang = "user-settings-ui-lang",
}

export default (): ReactElement => {
const potentialUser = getCurrentUser();
return potentialUser ? <UserSettings user={potentialUser} /> : <Fragment />;
};
export default function UserSettingsGetUser(): ReactElement {
const [potentialUser, setPotentialUser] = useState(getCurrentUser());

export function UserSettings(props: { user: User }): ReactElement {
return potentialUser ? (
<UserSettings user={potentialUser} setUser={setPotentialUser} />
) : (
<Fragment />
);
}

export function UserSettings(props: {
user: User;
setUser: (user?: User) => void;
}): ReactElement {
const [name, setName] = useState(props.user.name);
const [phone, setPhone] = useState(props.user.phone);
const [email, setEmail] = useState(props.user.email);
Expand Down Expand Up @@ -70,9 +78,11 @@ export function UserSettings(props: { user: User }): ReactElement {
phone,
email: punycode.toUnicode(email),
uiLang,
hasAvatar: !!avatar,
});
updateLangFromUser();
enqueueSnackbar(t("userSettings.updateSuccess"));
props.setUser(getCurrentUser());
} else {
setEmailTaken(true);
}
Expand Down
53 changes: 51 additions & 2 deletions src/components/UserSettings/tests/UserSettings.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import userEvent from "@testing-library/user-event";
import "tests/reactI18nextMock";

import { User } from "api/models";
import {
import UserSettingsGetUser, {
UserSettings,
UserSettingsIds,
} from "components/UserSettings/UserSettings";
import { newUser } from "types/user";

const mockGetAvatar = jest.fn();
const mockGetCurrentUser = jest.fn();
const mockIsEmailTaken = jest.fn();
const mockSetUser = jest.fn();
const mockUpdateUser = jest.fn();

jest.mock("notistack", () => ({
Expand All @@ -23,6 +26,11 @@ jest.mock("backend", () => ({
isEmailTaken: (...args: any[]) => mockIsEmailTaken(...args),
updateUser: (...args: any[]) => mockUpdateUser(...args),
}));
jest.mock("backend/localStorage", () => ({
getAvatar: (...args: any[]) => mockGetAvatar(...args),
getCurrentUser: (...args: any[]) => mockGetCurrentUser(...args),
}));

// Mock "i18n", else `thrown: "Error: Error: connect ECONNREFUSED ::1:80 [...]`
jest.mock("i18n", () => ({
updateLangFromUser: jest.fn(),
Expand All @@ -37,7 +45,10 @@ const mockUser = (): User => {
};

const setupMocks = (): void => {
mockGetAvatar.mockReturnValue("");
mockGetCurrentUser.mockReturnValue(mockUser());
mockIsEmailTaken.mockResolvedValue(false);
mockSetUser.mockImplementation(async (user?: User) => {});
mockUpdateUser.mockImplementation((user: User) => user);
};

Expand All @@ -50,7 +61,13 @@ afterEach(cleanup);

const renderUserSettings = async (user = mockUser()): Promise<void> => {
await act(async () => {
render(<UserSettings user={user} />);
render(<UserSettings user={user} setUser={mockSetUser} />);
});
};

const renderUserSettingsGetUser = async (): Promise<void> => {
await act(async () => {
render(<UserSettingsGetUser />);
});
};

Expand Down Expand Up @@ -90,6 +107,38 @@ describe("UserSettings", () => {
await typeAndCheckEnabled(UserSettingsIds.FieldPhone);
});

it("disables button when change is saved", async () => {
const agent = userEvent.setup();
const stringToType = "?";
const user = mockUser();
await renderUserSettingsGetUser();
const submitButton = screen.getByTestId(UserSettingsIds.ButtonSubmit);

const typeAndCheckEnabled = async (id: UserSettingsIds): Promise<void> => {
expect(submitButton).toBeDisabled();
await act(async () => {
await agent.type(screen.getByTestId(id), stringToType);
});
expect(submitButton).toBeEnabled();
await act(async () => {
await agent.click(submitButton);
});
expect(submitButton).toBeDisabled();
};

user.email += stringToType;
mockGetCurrentUser.mockReturnValueOnce({ ...user });
await typeAndCheckEnabled(UserSettingsIds.FieldEmail);

user.name += stringToType;
mockGetCurrentUser.mockReturnValueOnce({ ...user });
await typeAndCheckEnabled(UserSettingsIds.FieldName);

user.phone += stringToType;
mockGetCurrentUser.mockReturnValueOnce({ ...user });
await typeAndCheckEnabled(UserSettingsIds.FieldPhone);
});

it("updates user when something is changed and submitted", async () => {
const agent = userEvent.setup();
await renderUserSettings();
Expand Down

0 comments on commit 18a7210

Please sign in to comment.