Skip to content

Commit a21b53a

Browse files
authored
Merge pull request #122 from HassanBahati/ft-add-useUpdateCurrentUserMutation
feat: add useUpdateCurrentUserMutation hook
2 parents a5a8d2b + b2ae4ad commit a21b53a

File tree

3 files changed

+209
-1
lines changed

3 files changed

+209
-1
lines changed

packages/react/src/auth/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export { useSignInWithCredentialMutation } from "./useSignInWithCredentialMutati
2727
// useSignInWithPopupMutation
2828
// useSignInWithRedirectMutation
2929
// useSignOutMutation
30-
// useUpdateCurrentUserMutation
30+
export { useUpdateCurrentUserMutation } from "./useUpdateCurrentUserMutation";
3131
// useValidatePasswordMutation
3232
// useVerifyPasswordResetCodeMutation
3333
// useDeleteUserMutation
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import React from "react";
2+
import { describe, expect, test, beforeEach, afterEach, vi } from "vitest";
3+
import { renderHook, act, waitFor } from "@testing-library/react";
4+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
5+
import { useUpdateCurrentUserMutation } from "./useUpdateCurrentUserMutation";
6+
import { auth, wipeAuth } from "~/testing-utils";
7+
import {
8+
createUserWithEmailAndPassword,
9+
signInWithEmailAndPassword,
10+
type User,
11+
} from "firebase/auth";
12+
13+
const queryClient = new QueryClient({
14+
defaultOptions: {
15+
queries: { retry: false },
16+
mutations: { retry: false },
17+
},
18+
});
19+
20+
const wrapper = ({ children }: { children: React.ReactNode }) => (
21+
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
22+
);
23+
24+
describe("useUpdateCurrentUserMutation", () => {
25+
let currentUser: User | null = null;
26+
27+
beforeEach(async () => {
28+
queryClient.clear();
29+
await wipeAuth();
30+
});
31+
32+
afterEach(async () => {
33+
await auth.signOut();
34+
});
35+
36+
test("successfully updates current user", async () => {
37+
const email = "[email protected]";
38+
const password = "tanstackQueryFirebase#123";
39+
40+
await createUserWithEmailAndPassword(auth, email, password);
41+
const userCredential = await signInWithEmailAndPassword(
42+
auth,
43+
email,
44+
password
45+
);
46+
const newUser = userCredential.user;
47+
48+
const { result } = renderHook(() => useUpdateCurrentUserMutation(auth), {
49+
wrapper,
50+
});
51+
52+
await act(async () => {
53+
result.current.mutate(newUser);
54+
});
55+
56+
await waitFor(() => expect(result.current.isSuccess).toBe(true));
57+
58+
expect(auth.currentUser?.uid).toBe(newUser.uid);
59+
expect(auth.currentUser?.email).toBe(email);
60+
});
61+
62+
test("successfully sets current user to null", async () => {
63+
const email = "[email protected]";
64+
const password = "tanstackQueryFirebase#123";
65+
66+
await createUserWithEmailAndPassword(auth, email, password);
67+
await signInWithEmailAndPassword(auth, email, password);
68+
69+
const { result } = renderHook(() => useUpdateCurrentUserMutation(auth), {
70+
wrapper,
71+
});
72+
73+
await act(async () => {
74+
result.current.mutate(null);
75+
});
76+
77+
await waitFor(() => expect(result.current.isSuccess).toBe(true));
78+
79+
expect(auth.currentUser).toBeNull();
80+
});
81+
82+
test("handles update error when user is invalid", async () => {
83+
const invalidUser = { uid: "invalid-uid" } as User;
84+
85+
const { result } = renderHook(() => useUpdateCurrentUserMutation(auth), {
86+
wrapper,
87+
});
88+
89+
await act(async () => {
90+
result.current.mutate(invalidUser);
91+
});
92+
93+
await waitFor(() => expect(result.current.isError).toBe(true));
94+
95+
expect(result.current.error).toBeDefined();
96+
// TODO: Assert for firebase error
97+
});
98+
99+
test("calls onSuccess callback after successful update", async () => {
100+
const email = "[email protected]";
101+
const password = "tanstackQueryFirebase#123";
102+
const onSuccessMock = vi.fn();
103+
104+
await createUserWithEmailAndPassword(auth, email, password);
105+
const userCredential = await signInWithEmailAndPassword(
106+
auth,
107+
email,
108+
password
109+
);
110+
const newUser = userCredential.user;
111+
112+
const { result } = renderHook(
113+
() =>
114+
useUpdateCurrentUserMutation(auth, {
115+
onSuccess: onSuccessMock,
116+
}),
117+
{ wrapper }
118+
);
119+
120+
await act(async () => {
121+
result.current.mutate(newUser);
122+
});
123+
124+
await waitFor(() => expect(result.current.isSuccess).toBe(true));
125+
126+
expect(onSuccessMock).toHaveBeenCalled();
127+
expect(auth.currentUser?.email).toBe(email);
128+
});
129+
130+
test("calls onError callback on update failure", async () => {
131+
const onErrorMock = vi.fn();
132+
const error = new Error("Update failed");
133+
134+
// Mock updateCurrentUser to simulate error
135+
const mockUpdateUser = vi
136+
.spyOn(auth, "updateCurrentUser")
137+
.mockRejectedValueOnce(error);
138+
139+
const { result } = renderHook(
140+
() =>
141+
useUpdateCurrentUserMutation(auth, {
142+
onError: onErrorMock,
143+
}),
144+
{ wrapper }
145+
);
146+
147+
await act(async () => {
148+
result.current.mutate(null);
149+
});
150+
151+
await waitFor(() => expect(result.current.isError).toBe(true));
152+
153+
expect(result.current.error).toBe(error);
154+
expect(result.current.isSuccess).toBe(false);
155+
mockUpdateUser.mockRestore();
156+
});
157+
158+
test("handles concurrent update attempts", async () => {
159+
const email = "[email protected]";
160+
const password = "tanstackQueryFirebase#123";
161+
162+
await createUserWithEmailAndPassword(auth, email, password);
163+
const userCredential = await signInWithEmailAndPassword(
164+
auth,
165+
email,
166+
password
167+
);
168+
const newUser = userCredential.user;
169+
170+
const { result } = renderHook(() => useUpdateCurrentUserMutation(auth), {
171+
wrapper,
172+
});
173+
174+
await act(async () => {
175+
// Attempt multiple concurrent updates
176+
result.current.mutate(newUser);
177+
result.current.mutate(newUser);
178+
});
179+
180+
await waitFor(() => expect(result.current.isSuccess).toBe(true));
181+
182+
expect(auth.currentUser?.uid).toBe(newUser.uid);
183+
expect(auth.currentUser?.email).toBe(email);
184+
});
185+
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { useMutation, type UseMutationOptions } from "@tanstack/react-query";
2+
import {
3+
type Auth,
4+
AuthError,
5+
updateCurrentUser,
6+
type User,
7+
} from "firebase/auth";
8+
9+
type AuthUseMutationOptions<
10+
TData = unknown,
11+
TError = Error,
12+
TVariables = void
13+
> = Omit<UseMutationOptions<TData, TError, TVariables>, "mutationFn">;
14+
15+
export function useUpdateCurrentUserMutation(
16+
auth: Auth,
17+
options?: AuthUseMutationOptions<void, AuthError, User | null>
18+
) {
19+
return useMutation<void, AuthError, User | null>({
20+
...options,
21+
mutationFn: (user) => updateCurrentUser(auth, user),
22+
});
23+
}

0 commit comments

Comments
 (0)