Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/react/src/auth/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// useAuthStateReadyQuery (Auth)
// useConfirmationResultConfirmMutation (ConfirmationResult)
// useUserDeleteMutation (User)
// userUserGetIdTokenResultMutation (User)
export { userUserGetIdTokenResultMutation } from "./userUserGetIdTokenResultMutation";
// useUserGetIdTokenMutation (User)
// useUserReloadMutation (User)
// useVerifyPhoneNumberMutation (PhoneAuthProvider)
Expand Down
139 changes: 139 additions & 0 deletions packages/react/src/auth/userUserGetIdTokenResultMutation.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { act, renderHook, waitFor } from "@testing-library/react";
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
} from "firebase/auth";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import { auth, wipeAuth } from "~/testing-utils";
import { userUserGetIdTokenResultMutation } from "./userUserGetIdTokenResultMutation";
import { queryClient, wrapper } from "../../utils";

describe("userUserGetIdTokenResultMutation", () => {
const email = "[email protected]";
const password = "TanstackQueryFirebase#123";

beforeEach(async () => {
queryClient.clear();
await wipeAuth();
await createUserWithEmailAndPassword(auth, email, password);
});

afterEach(async () => {
vi.clearAllMocks();
await auth.signOut();
});

test("successfully retrieves ID token result with all properties", async () => {
const userCredential = await signInWithEmailAndPassword(
auth,
email,
password
);
const { user } = userCredential;

const { result } = renderHook(
() => userUserGetIdTokenResultMutation(user),
{ wrapper }
);

await act(async () => {
await result.current.mutateAsync();
});

await waitFor(() => expect(result.current.isSuccess).toBe(true));

const tokenResult = result.current.data;

// Verify all IdTokenResult properties exist and have correct types
expect(tokenResult?.authTime).toBeTypeOf("string");
expect(tokenResult?.issuedAtTime).toBeTypeOf("string");
expect(tokenResult?.expirationTime).toBeTypeOf("string");
expect(tokenResult?.token).toBeTypeOf("string");
expect(tokenResult?.claims).toBeTypeOf("object");
expect(
typeof tokenResult?.signInProvider === "string" ||
tokenResult?.signInProvider === null
).toBe(true);
expect(
typeof tokenResult?.signInSecondFactor === "string" ||
tokenResult?.signInSecondFactor === null
).toBe(true);
});

test("can get token result with forceRefresh option", async () => {
const userCredential = await signInWithEmailAndPassword(
auth,
email,
password
);
const { user } = userCredential;

const { result } = renderHook(
() =>
userUserGetIdTokenResultMutation(user, {
auth: { forceRefresh: true },
}),
{ wrapper }
);

await act(async () => {
await result.current.mutateAsync();
});

await waitFor(() => expect(result.current.isSuccess).toBe(true));

const tokenResult = result.current.data;
expect(tokenResult?.token).toBeTypeOf("string");
expect(tokenResult?.claims).toBeTypeOf("object");
});

test("executes onSuccess callback with token result", async () => {
const userCredential = await signInWithEmailAndPassword(
auth,
email,
password
);
const { user } = userCredential;
const onSuccess = vi.fn();

const { result } = renderHook(
() => userUserGetIdTokenResultMutation(user, { onSuccess }),
{ wrapper }
);

await act(async () => {
await result.current.mutateAsync();
});

await waitFor(() => expect(onSuccess).toHaveBeenCalled());

const tokenResult = onSuccess.mock.calls[0][0];
expect(tokenResult.token).toBeTypeOf("string");
expect(tokenResult.claims).toBeTypeOf("object");
expect(tokenResult.authTime).toBeTypeOf("string");
});

test("verifies signInProvider for password authentication", async () => {
const userCredential = await signInWithEmailAndPassword(
auth,
email,
password
);
const { user } = userCredential;

const { result } = renderHook(
() => userUserGetIdTokenResultMutation(user),
{ wrapper }
);

await act(async () => {
await result.current.mutateAsync();
});

await waitFor(() => expect(result.current.isSuccess).toBe(true));
const tokenResult = result.current.data;
expect(tokenResult?.signInProvider).toBe("password");
expect(tokenResult?.signInSecondFactor).toBeNull();
});
});
30 changes: 30 additions & 0 deletions packages/react/src/auth/userUserGetIdTokenResultMutation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { type UseMutationOptions, useMutation } from "@tanstack/react-query";
import {
type User,
type AuthError,
getIdTokenResult,
type IdTokenResult,
} from "firebase/auth";

type AuthUseMutationOptions<
TData = unknown,
TError = Error,
TVariables = void
> = Omit<UseMutationOptions<TData, TError, TVariables>, "mutationFn"> & {
auth?: {
forceRefresh?: boolean;
};
};

export function userUserGetIdTokenResultMutation(
user: User,
options?: AuthUseMutationOptions<IdTokenResult, AuthError>
) {
const { auth, ...mutationOptions } = options || {};
const forceRefresh = auth?.forceRefresh;

return useMutation<IdTokenResult, AuthError>({
...mutationOptions,
mutationFn: () => getIdTokenResult(user, forceRefresh),
});
}
Loading