Skip to content

Commit a4fcff1

Browse files
authored
feat(react/auth): add useConfirmPasswordResetMutation (#153)
1 parent a4acc27 commit a4fcff1

File tree

3 files changed

+146
-1
lines changed

3 files changed

+146
-1
lines changed

packages/react/src/auth/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
export { useCheckActionCodeMutation } from "./useCheckActionCodeMutation";
1414
export { useApplyActionCodeMutation } from "./useApplyActionCodeMutation";
1515
// useCheckActionCodeMutation
16-
// useConfirmPasswordResetMutation
16+
export { useConfirmPasswordResetMutation } from "./useConfirmPasswordResetMutation";
1717
// useCreateUserWithEmailAndPasswordMutation
1818
// useGetRedirectResultQuery
1919
export { useGetRedirectResultQuery } from "./useGetRedirectResultQuery";
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { act, renderHook, waitFor } from "@testing-library/react";
2+
import {
3+
createUserWithEmailAndPassword,
4+
sendPasswordResetEmail,
5+
} from "firebase/auth";
6+
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
7+
import { auth, expectFirebaseError, wipeAuth } from "~/testing-utils";
8+
import { useConfirmPasswordResetMutation } from "./useConfirmPasswordResetMutation";
9+
import { waitForPasswordResetCode } from "./utils";
10+
import { queryClient, wrapper } from "../../utils";
11+
12+
describe("useConfirmPasswordResetMutation", () => {
13+
const email = "[email protected]";
14+
const password = "TanstackQueryFirebase#123";
15+
const newPassword = "NewSecurePassword#456";
16+
17+
beforeEach(async () => {
18+
queryClient.clear();
19+
await wipeAuth();
20+
await createUserWithEmailAndPassword(auth, email, password);
21+
});
22+
23+
afterEach(async () => {
24+
vi.clearAllMocks();
25+
await auth.signOut();
26+
});
27+
28+
test("successfully resets password", async () => {
29+
await sendPasswordResetEmail(auth, email);
30+
const oobCode = await waitForPasswordResetCode(email);
31+
32+
const { result } = renderHook(() => useConfirmPasswordResetMutation(auth), {
33+
wrapper,
34+
});
35+
36+
await act(async () => {
37+
await result.current.mutateAsync({ oobCode: oobCode!, newPassword });
38+
});
39+
40+
await waitFor(() => expect(result.current.isSuccess).toBe(true));
41+
});
42+
43+
test("handles invalid action code", async () => {
44+
const invalidCode = "invalid-action-code";
45+
46+
const { result } = renderHook(() => useConfirmPasswordResetMutation(auth), {
47+
wrapper,
48+
});
49+
50+
await act(async () => {
51+
try {
52+
await result.current.mutateAsync({ oobCode: invalidCode, newPassword });
53+
} catch (error) {
54+
expectFirebaseError(error, "auth/invalid-action-code");
55+
}
56+
});
57+
58+
await waitFor(() => expect(result.current.isError).toBe(true));
59+
expect(result.current.error).toBeDefined();
60+
expectFirebaseError(result.current.error, "auth/invalid-action-code");
61+
});
62+
63+
test("handles empty action code", async () => {
64+
const { result } = renderHook(() => useConfirmPasswordResetMutation(auth), {
65+
wrapper,
66+
});
67+
68+
await act(async () => {
69+
try {
70+
await result.current.mutateAsync({ oobCode: "", newPassword });
71+
} catch (error) {
72+
expectFirebaseError(error, "auth/internal-error");
73+
}
74+
});
75+
76+
await waitFor(() => expect(result.current.isError).toBe(true));
77+
expect(result.current.error).toBeDefined();
78+
expectFirebaseError(result.current.error, "auth/internal-error");
79+
});
80+
81+
test("executes onSuccess callback", async () => {
82+
await sendPasswordResetEmail(auth, email);
83+
const oobCode = await waitForPasswordResetCode(email);
84+
const onSuccess = vi.fn();
85+
86+
const { result } = renderHook(
87+
() => useConfirmPasswordResetMutation(auth, { onSuccess }),
88+
{ wrapper }
89+
);
90+
91+
await act(async () => {
92+
await result.current.mutateAsync({ oobCode: oobCode!, newPassword });
93+
});
94+
95+
await waitFor(() => expect(onSuccess).toHaveBeenCalled());
96+
});
97+
98+
test("executes onError callback", async () => {
99+
const invalidCode = "invalid-action-code";
100+
const onError = vi.fn();
101+
102+
const { result } = renderHook(
103+
() => useConfirmPasswordResetMutation(auth, { onError }),
104+
{ wrapper }
105+
);
106+
107+
await act(async () => {
108+
try {
109+
await result.current.mutateAsync({ oobCode: invalidCode, newPassword });
110+
} catch (error) {
111+
expectFirebaseError(error, "auth/invalid-action-code");
112+
}
113+
});
114+
115+
await waitFor(() => expect(onError).toHaveBeenCalled());
116+
expect(onError.mock.calls[0][0]).toBeDefined();
117+
expectFirebaseError(result.current.error, "auth/invalid-action-code");
118+
});
119+
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { type UseMutationOptions, useMutation } from "@tanstack/react-query";
2+
import { type Auth, type AuthError, confirmPasswordReset } from "firebase/auth";
3+
4+
type AuthUseMutationOptions<
5+
TData = unknown,
6+
TError = Error,
7+
TVariables = void
8+
> = Omit<UseMutationOptions<TData, TError, TVariables>, "mutationFn">;
9+
10+
export function useConfirmPasswordResetMutation(
11+
auth: Auth,
12+
options?: AuthUseMutationOptions<
13+
void,
14+
AuthError,
15+
{ oobCode: string; newPassword: string }
16+
>
17+
) {
18+
return useMutation<void, AuthError, { oobCode: string; newPassword: string }>(
19+
{
20+
...options,
21+
mutationFn: ({ oobCode, newPassword }) => {
22+
return confirmPasswordReset(auth, oobCode, newPassword);
23+
},
24+
}
25+
);
26+
}

0 commit comments

Comments
 (0)