Skip to content

Commit c47ec78

Browse files
HassanBahatiEhesp
andauthored
feat(react/auth): add useCreateUserWithEmailAndPasswordMutation (#154)
Co-authored-by: Elliot Hesp <[email protected]>
1 parent 61d235d commit c47ec78

File tree

3 files changed

+176
-0
lines changed

3 files changed

+176
-0
lines changed

packages/react/src/auth/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
export { useCheckActionCodeMutation } from "./useCheckActionCodeMutation";
1414
export { useApplyActionCodeMutation } from "./useApplyActionCodeMutation";
1515
// useCheckActionCodeMutation
16+
// useConfirmPasswordResetMutation
17+
export { useCreateUserWithEmailAndPasswordMutation } from "./useCreateUserWithEmailAndPasswordMutation";
18+
// useFetchSignInMethodsForEmailQuery
1619
export { useConfirmPasswordResetMutation } from "./useConfirmPasswordResetMutation";
1720
// useCreateUserWithEmailAndPasswordMutation
1821
// useGetRedirectResultQuery
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import { act, renderHook, waitFor } from "@testing-library/react";
2+
import { afterEach, describe, expect, test, vi, beforeEach } from "vitest";
3+
import { auth, expectFirebaseError, wipeAuth } from "~/testing-utils";
4+
import { useCreateUserWithEmailAndPasswordMutation } from "./useCreateUserWithEmailAndPasswordMutation";
5+
import { queryClient, wrapper } from "../../utils";
6+
7+
describe("useCreateUserWithEmailAndPasswordMutation", () => {
8+
const email = "[email protected]";
9+
const password = "TanstackQueryFirebase#123";
10+
11+
beforeEach(async () => {
12+
queryClient.clear();
13+
await wipeAuth();
14+
});
15+
16+
afterEach(async () => {
17+
vi.clearAllMocks();
18+
});
19+
20+
test("successfully creates user", async () => {
21+
const { result } = renderHook(
22+
() => useCreateUserWithEmailAndPasswordMutation(auth),
23+
{
24+
wrapper,
25+
}
26+
);
27+
28+
await act(async () => {
29+
const userCredential = await result.current.mutateAsync({
30+
email,
31+
password,
32+
});
33+
expect(userCredential.user.email).toBe(email);
34+
});
35+
36+
await waitFor(() => expect(result.current.isSuccess).toBe(true));
37+
});
38+
39+
test("handles existing user", async () => {
40+
const { result: firstAttempt } = renderHook(
41+
() => useCreateUserWithEmailAndPasswordMutation(auth),
42+
{
43+
wrapper,
44+
}
45+
);
46+
47+
await act(async () => {
48+
await firstAttempt.current.mutateAsync({ email, password });
49+
});
50+
51+
const { result } = renderHook(
52+
() => useCreateUserWithEmailAndPasswordMutation(auth),
53+
{
54+
wrapper,
55+
}
56+
);
57+
58+
await act(async () => {
59+
try {
60+
await result.current.mutateAsync({ email, password });
61+
} catch (error) {
62+
expectFirebaseError(error, "auth/email-already-in-use");
63+
}
64+
});
65+
66+
await waitFor(() => expect(result.current.isError).toBe(true));
67+
expectFirebaseError(result.current.error, "auth/email-already-in-use");
68+
});
69+
70+
test("handles weak password", async () => {
71+
const weakPassword = "weak";
72+
const { result } = renderHook(
73+
() => useCreateUserWithEmailAndPasswordMutation(auth),
74+
{
75+
wrapper,
76+
}
77+
);
78+
79+
await act(async () => {
80+
try {
81+
await result.current.mutateAsync({ email, password: weakPassword });
82+
} catch (error) {
83+
expectFirebaseError(error, "auth/weak-password");
84+
}
85+
});
86+
87+
await waitFor(() => expect(result.current.isError).toBe(true));
88+
expectFirebaseError(result.current.error, "auth/weak-password");
89+
});
90+
91+
test("executes onSuccess callback", async () => {
92+
const onSuccess = vi.fn();
93+
94+
const { result } = renderHook(
95+
() => useCreateUserWithEmailAndPasswordMutation(auth, { onSuccess }),
96+
{ wrapper }
97+
);
98+
99+
await act(async () => {
100+
await result.current.mutateAsync({ email, password });
101+
});
102+
103+
await waitFor(() => expect(onSuccess).toHaveBeenCalled());
104+
});
105+
106+
test("executes onError callback", async () => {
107+
const existingEmail = "[email protected]";
108+
const onError = vi.fn();
109+
110+
const { result: firstAttempt } = renderHook(
111+
() => useCreateUserWithEmailAndPasswordMutation(auth),
112+
{
113+
wrapper,
114+
}
115+
);
116+
117+
await act(async () => {
118+
await firstAttempt.current.mutateAsync({
119+
email: existingEmail,
120+
password,
121+
});
122+
});
123+
124+
const { result } = renderHook(
125+
() => useCreateUserWithEmailAndPasswordMutation(auth, { onError }),
126+
{ wrapper }
127+
);
128+
129+
await act(async () => {
130+
try {
131+
await result.current.mutateAsync({ email: existingEmail, password });
132+
} catch (error) {
133+
expectFirebaseError(error, "auth/email-already-in-use");
134+
}
135+
});
136+
137+
await waitFor(() => expect(onError).toHaveBeenCalled());
138+
expect(onError.mock.calls[0][0]).toBeDefined();
139+
expectFirebaseError(result.current.error, "auth/email-already-in-use");
140+
});
141+
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { type UseMutationOptions, useMutation } from "@tanstack/react-query";
2+
import {
3+
type Auth,
4+
type AuthError,
5+
type UserCredential,
6+
createUserWithEmailAndPassword,
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 useCreateUserWithEmailAndPasswordMutation(
16+
auth: Auth,
17+
options?: AuthUseMutationOptions<
18+
UserCredential,
19+
AuthError,
20+
{ email: string; password: string }
21+
>
22+
) {
23+
return useMutation<
24+
UserCredential,
25+
AuthError,
26+
{ email: string; password: string }
27+
>({
28+
...options,
29+
mutationFn: ({ email, password }) =>
30+
createUserWithEmailAndPassword(auth, email, password),
31+
});
32+
}

0 commit comments

Comments
 (0)