Skip to content

Commit

Permalink
feat(react/auth): add useGetRedirectResultQuery
Browse files Browse the repository at this point in the history
  • Loading branch information
HassanBahati committed Feb 4, 2025
1 parent 5e6e46a commit 9543213
Show file tree
Hide file tree
Showing 3 changed files with 234 additions and 1 deletion.
2 changes: 1 addition & 1 deletion packages/react/src/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// useConfirmPasswordResetMutation
// useCreateUserWithEmailAndPasswordMutation
// useFetchSignInMethodsForEmailQuery
// useGetRedirectResultQuery
export { useGetRedirectResultQuery } from "./useGetRedirectResultQuery";
// useRevokeAccessTokenMutation
// useSendPasswordResetEmailMutation
export { useSendSignInLinkToEmailMutation } from "./useSendSignInLinkToEmailMutation";
Expand Down
207 changes: 207 additions & 0 deletions packages/react/src/auth/useGetRedirectResultQuery.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import { renderHook, waitFor } from "@testing-library/react";
import { describe, expect, test, vi, beforeEach, afterEach } from "vitest";
import {
type UserCredential,
type PopupRedirectResolver,
getRedirectResult,
} from "firebase/auth";
import { auth, wipeAuth } from "~/testing-utils";
import { useGetRedirectResultQuery } from "./useGetRedirectResultQuery";
import { queryClient, wrapper } from "../../utils";

vi.mock("firebase/auth", async () => {
const actual = await vi.importActual("firebase/auth");
return {
...actual,
getRedirectResult: vi.fn(),
};
});

describe("useGetRedirectResultQuery", () => {
beforeEach(async () => {
queryClient.clear();
await wipeAuth();
});

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

test("returns user credential on successful redirect", async () => {
const mockUserCredential = {
user: {
uid: "test-uid",
email: "test@example.com",
},
operationType: "signIn",
providerId: "google.com",
} as unknown as UserCredential;

vi.mocked(getRedirectResult).mockResolvedValueOnce(mockUserCredential);

const { result } = renderHook(
() =>
useGetRedirectResultQuery(auth, {
queryKey: ["redirectResult"],
}),
{ wrapper }
);

expect(result.current.isLoading).toBe(true);

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

expect(result.current.data).toEqual(mockUserCredential);
expect(getRedirectResult).toHaveBeenCalledWith(auth, undefined);
});

test("uses custom resolver when provided", async () => {
const mockResolver = {} as PopupRedirectResolver;
const mockUserCredential = {
user: {
uid: "test-uid",
email: "test@example.com",
},
} as unknown as UserCredential;

vi.mocked(getRedirectResult).mockResolvedValueOnce(mockUserCredential);

const { result } = renderHook(
() =>
useGetRedirectResultQuery(auth, {
queryKey: ["redirectResult"],
auth: { resolver: mockResolver },
}),
{ wrapper }
);

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

expect(getRedirectResult).toHaveBeenCalledWith(auth, mockResolver);
});

test("uses different query keys for different configs", async () => {
const mockUserCredential1 = {
user: { uid: "user1" },
} as unknown as UserCredential;

const mockUserCredential2 = {
user: { uid: "user2" },
} as unknown as UserCredential;

vi.mocked(getRedirectResult)
.mockResolvedValueOnce(mockUserCredential1)
.mockResolvedValueOnce(mockUserCredential2);

// Render first hook with default key
const { result: result1 } = renderHook(
() =>
useGetRedirectResultQuery(auth, {
queryKey: ["redirectResult", "config1"],
}),
{ wrapper }
);

// Render second hook with different key
const { result: result2 } = renderHook(
() =>
useGetRedirectResultQuery(auth, {
queryKey: ["redirectResult", "config2"],
}),
{ wrapper }
);

await waitFor(() => {
expect(result1.current.isSuccess).toBe(true);
expect(result2.current.isSuccess).toBe(true);
});

expect(result1.current.data).toEqual(mockUserCredential1);
expect(result2.current.data).toEqual(mockUserCredential2);
expect(getRedirectResult).toHaveBeenCalledTimes(2);
});

test("adheres to enabled option", async () => {
const mockUserCredential = {
user: { uid: "test-uid" },
} as unknown as UserCredential;

vi.mocked(getRedirectResult).mockResolvedValueOnce(mockUserCredential);

const { result } = renderHook(
() =>
useGetRedirectResultQuery(auth, {
queryKey: ["redirectResult"],
enabled: false,
}),
{ wrapper }
);

expect(result.current.isLoading).toBe(false);
expect(getRedirectResult).not.toHaveBeenCalled();
});

test("shares data between hooks with same query key", async () => {
const mockUserCredential = {
user: { uid: "test-uid" },
} as unknown as UserCredential;

vi.mocked(getRedirectResult).mockResolvedValueOnce(mockUserCredential);

// Render first instance
const { result: result1 } = renderHook(
() =>
useGetRedirectResultQuery(auth, {
queryKey: ["redirectResult"],
}),
{ wrapper }
);

// Render second instance with same key
const { result: result2 } = renderHook(
() =>
useGetRedirectResultQuery(auth, {
queryKey: ["redirectResult"],
}),
{ wrapper }
);

await waitFor(() => {
expect(result1.current.isSuccess).toBe(true);
expect(result2.current.isSuccess).toBe(true);
});

expect(result1.current.data).toEqual(result2.current.data);
expect(getRedirectResult).toHaveBeenCalledTimes(1);
});

test("stale time prevents refetch", async () => {
const mockUserCredential = {
user: { uid: "test-uid" },
} as unknown as UserCredential;

vi.mocked(getRedirectResult).mockResolvedValue(mockUserCredential);

const { result, rerender } = renderHook(
() =>
useGetRedirectResultQuery(auth, {
queryKey: ["redirectResult"],
staleTime: 1000,
}),
{ wrapper }
);

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

// Rerender while data is still fresh
rerender();

expect(getRedirectResult).toHaveBeenCalledTimes(1);
});
});
26 changes: 26 additions & 0 deletions packages/react/src/auth/useGetRedirectResultQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { type UseQueryOptions, useQuery } from "@tanstack/react-query";
import {
type Auth,
type AuthError,
getRedirectResult,
type PopupRedirectResolver,
type UserCredential,
} from "firebase/auth";

type AuthUseQueryOptions<TData = unknown, TError = Error> = Omit<
UseQueryOptions<TData, TError, void>,
"queryFn"
> & { auth?: { resolver?: PopupRedirectResolver } };

export function useGetRedirectResultQuery(
auth: Auth,
options: AuthUseQueryOptions<UserCredential | null, AuthError>
) {
const { auth: authOptions, ...queryOptions } = options;
const resolver = authOptions?.resolver;

return useQuery<UserCredential | null, AuthError, void>({
...queryOptions,
queryFn: () => getRedirectResult(auth, resolver),
});
}

0 comments on commit 9543213

Please sign in to comment.