-
Notifications
You must be signed in to change notification settings - Fork 70
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(react/auth): add useGetRedirectResultQuery
- Loading branch information
1 parent
5e6e46a
commit 9543213
Showing
3 changed files
with
234 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
207 changes: 207 additions & 0 deletions
207
packages/react/src/auth/useGetRedirectResultQuery.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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), | ||
}); | ||
} |