Skip to content

Commit ace63f7

Browse files
authored
Merge pull request #1249 from firebase/@invertase/bb-28
2 parents a1c28e3 + 1be0547 commit ace63f7

File tree

4 files changed

+36
-34
lines changed

4 files changed

+36
-34
lines changed

packages/core/src/auth.test.ts

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1257,8 +1257,6 @@ describe("completeEmailLinkSignIn", () => {
12571257

12581258
expect(_isSignInWithEmailLink).toHaveBeenCalledWith(mockUI.auth, currentUrl);
12591259
expect(result).toBeNull();
1260-
1261-
expect(vi.mocked(mockUI.setState).mock.calls).toEqual([["idle"]]);
12621260
});
12631261

12641262
it("should return null when no email is stored in localStorage", async () => {
@@ -1271,7 +1269,6 @@ describe("completeEmailLinkSignIn", () => {
12711269

12721270
expect(_isSignInWithEmailLink).toHaveBeenCalledWith(mockUI.auth, currentUrl);
12731271
expect(result).toBeNull();
1274-
expect(vi.mocked(mockUI.setState).mock.calls).toEqual([["idle"]]);
12751272
});
12761273

12771274
it("should complete email link sign-in with no behavior", async () => {
@@ -1294,7 +1291,8 @@ describe("completeEmailLinkSignIn", () => {
12941291
expect(EmailAuthProvider.credentialWithLink).toHaveBeenCalledWith(email, currentUrl);
12951292
expect(_signInWithCredential).toHaveBeenCalledWith(mockUI.auth, emailLinkCredential);
12961293
expect(result).toBe(mockCredential);
1297-
expect(vi.mocked(mockUI.setState).mock.calls).toEqual([["pending"], ["pending"], ["idle"], ["idle"]]);
1294+
1295+
expect(vi.mocked(mockUI.setState).mock.calls).toEqual([["pending"], ["idle"]]);
12981296
expect(window.localStorage.getItem("emailForSignIn")).toBeNull();
12991297
});
13001298

@@ -1315,16 +1313,18 @@ describe("completeEmailLinkSignIn", () => {
13151313
const result = await completeEmailLinkSignIn(mockUI, currentUrl);
13161314

13171315
expect(_isSignInWithEmailLink).toHaveBeenCalledWith(mockUI.auth, currentUrl);
1316+
// Behavior is checked by signInWithCredential (called via signInWithEmailLink)
13181317
expect(hasBehavior).toHaveBeenCalledWith(mockUI, "autoUpgradeAnonymousCredential");
13191318
expect(EmailAuthProvider.credentialWithLink).toHaveBeenCalledWith(email, currentUrl);
13201319
expect(getBehavior).toHaveBeenCalledWith(mockUI, "autoUpgradeAnonymousCredential");
13211320
expect(mockBehavior).toHaveBeenCalledWith(mockUI, emailLinkCredential);
13221321
expect(result).toBe(mockResult);
1322+
// State is managed by signInWithCredential
13231323
expect(vi.mocked(mockUI.setState).mock.calls).toEqual([["pending"], ["idle"]]);
13241324
expect(window.localStorage.getItem("emailForSignIn")).toBeNull();
13251325
});
13261326

1327-
it("should fall back to signInWithEmailLink when autoUpgradeAnonymousCredential behavior returns undefined", async () => {
1327+
it("should fall back to _signInWithCredential when autoUpgradeAnonymousCredential behavior returns undefined", async () => {
13281328
const mockUI = createMockUI();
13291329
const currentUrl = "https://example.com/auth?oobCode=abc123";
13301330
const email = "[email protected]";
@@ -1342,17 +1342,19 @@ describe("completeEmailLinkSignIn", () => {
13421342
const result = await completeEmailLinkSignIn(mockUI, currentUrl);
13431343

13441344
expect(_isSignInWithEmailLink).toHaveBeenCalledWith(mockUI.auth, currentUrl);
1345+
// Behavior is checked by signInWithCredential (called via signInWithEmailLink)
13451346
expect(hasBehavior).toHaveBeenCalledWith(mockUI, "autoUpgradeAnonymousCredential");
13461347
expect(EmailAuthProvider.credentialWithLink).toHaveBeenCalledWith(email, currentUrl);
13471348
expect(getBehavior).toHaveBeenCalledWith(mockUI, "autoUpgradeAnonymousCredential");
13481349
expect(mockBehavior).toHaveBeenCalledWith(mockUI, emailLinkCredential);
13491350
expect(_signInWithCredential).toHaveBeenCalledWith(mockUI.auth, emailLinkCredential);
13501351
expect(result).toBe(mockResult);
1351-
expect(vi.mocked(mockUI.setState).mock.calls).toEqual([["pending"], ["pending"], ["idle"], ["idle"]]);
1352+
// State is managed by signInWithCredential
1353+
expect(vi.mocked(mockUI.setState).mock.calls).toEqual([["pending"], ["idle"]]);
13521354
expect(window.localStorage.getItem("emailForSignIn")).toBeNull();
13531355
});
13541356

1355-
it("should call handleFirebaseError if an error is thrown", async () => {
1357+
it("should propagate error from signInWithEmailLink", async () => {
13561358
const mockUI = createMockUI();
13571359
const currentUrl = "https://example.com/auth?oobCode=abc123";
13581360
const email = "[email protected]";
@@ -1364,16 +1366,21 @@ describe("completeEmailLinkSignIn", () => {
13641366
vi.mocked(hasBehavior).mockReturnValue(false);
13651367
vi.mocked(EmailAuthProvider.credentialWithLink).mockReturnValue(emailLinkCredential);
13661368
vi.mocked(_signInWithCredential).mockRejectedValue(error);
1369+
// handleFirebaseError throws, so we need to catch it
1370+
vi.mocked(handleFirebaseError).mockImplementation(() => {
1371+
throw new Error("Handled error");
1372+
});
13671373

1368-
const result = await completeEmailLinkSignIn(mockUI, currentUrl);
1374+
await expect(completeEmailLinkSignIn(mockUI, currentUrl)).rejects.toThrow("Handled error");
13691375

1376+
// Error is handled by signInWithCredential (called via signInWithEmailLink)
13701377
expect(handleFirebaseError).toHaveBeenCalledWith(mockUI, error);
1371-
expect(vi.mocked(mockUI.setState).mock.calls).toEqual([["pending"], ["pending"], ["idle"], ["idle"]]);
1378+
// State is managed by signInWithCredential
1379+
expect(vi.mocked(mockUI.setState).mock.calls).toEqual([["pending"], ["idle"]]);
13721380
expect(window.localStorage.getItem("emailForSignIn")).toBeNull();
1373-
expect(result).toBeUndefined();
13741381
});
13751382

1376-
it("should call handleFirebaseError if autoUpgradeAnonymousCredential behavior throws error", async () => {
1383+
it("should propagate error when autoUpgradeAnonymousCredential behavior throws", async () => {
13771384
const mockUI = createMockUI();
13781385
const currentUrl = "https://example.com/auth?oobCode=abc123";
13791386
const email = "[email protected]";
@@ -1386,13 +1393,18 @@ describe("completeEmailLinkSignIn", () => {
13861393
vi.mocked(EmailAuthProvider.credentialWithLink).mockReturnValue(emailLinkCredential);
13871394
const mockBehavior = vi.fn().mockRejectedValue(error);
13881395
vi.mocked(getBehavior).mockReturnValue(mockBehavior);
1396+
// handleFirebaseError throws, so we need to catch it
1397+
vi.mocked(handleFirebaseError).mockImplementation(() => {
1398+
throw new Error("Handled error");
1399+
});
13891400

1390-
const result = await completeEmailLinkSignIn(mockUI, currentUrl);
1401+
await expect(completeEmailLinkSignIn(mockUI, currentUrl)).rejects.toThrow("Handled error");
13911402

1403+
// Error is handled by signInWithCredential (called via signInWithEmailLink)
13921404
expect(handleFirebaseError).toHaveBeenCalledWith(mockUI, error);
1405+
// State is managed by signInWithCredential
13931406
expect(vi.mocked(mockUI.setState).mock.calls).toEqual([["pending"], ["idle"]]);
13941407
expect(window.localStorage.getItem("emailForSignIn")).toBeNull();
1395-
expect(result).toBeUndefined();
13961408
});
13971409

13981410
it("should clear email from localStorage even when error occurs", async () => {
@@ -1407,9 +1419,14 @@ describe("completeEmailLinkSignIn", () => {
14071419
vi.mocked(hasBehavior).mockReturnValue(false);
14081420
vi.mocked(EmailAuthProvider.credentialWithLink).mockReturnValue(emailLinkCredential);
14091421
vi.mocked(_signInWithCredential).mockRejectedValue(error);
1422+
// handleFirebaseError throws, but finally block should still run
1423+
vi.mocked(handleFirebaseError).mockImplementation(() => {
1424+
throw new Error("Handled error");
1425+
});
14101426

1411-
await completeEmailLinkSignIn(mockUI, currentUrl);
1427+
await expect(completeEmailLinkSignIn(mockUI, currentUrl)).rejects.toThrow("Handled error");
14121428

1429+
// finally block should still clean up localStorage
14131430
expect(window.localStorage.getItem("emailForSignIn")).toBeNull();
14141431
});
14151432

packages/core/src/auth.ts

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -314,23 +314,10 @@ export async function completeEmailLinkSignIn(ui: FirebaseUI, currentUrl: string
314314
const email = window.localStorage.getItem("emailForSignIn");
315315
if (!email) return null;
316316

317-
setPendingState(ui);
318-
319-
if (hasBehavior(ui, "autoUpgradeAnonymousCredential")) {
320-
const emailLinkCredential = EmailAuthProvider.credentialWithLink(email, currentUrl);
321-
const credential = await getBehavior(ui, "autoUpgradeAnonymousCredential")(ui, emailLinkCredential);
322-
323-
if (credential) {
324-
return handlePendingCredential(ui, credential);
325-
}
326-
}
327-
317+
// signInWithEmailLink handles behavior checks, credential creation, and error handling
328318
const result = await signInWithEmailLink(ui, email, currentUrl);
329319
return handlePendingCredential(ui, result);
330-
} catch (error) {
331-
handleFirebaseError(ui, error);
332320
} finally {
333-
ui.setState("idle");
334321
window.localStorage.removeItem("emailForSignIn");
335322
}
336323
}

packages/react/src/auth/forms/email-link-auth-form.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,14 @@ export function useEmailLinkAuthFormCompleteSignIn(onSignIn?: EmailLinkAuthFormP
7676
useEffect(() => {
7777
const completeSignIn = async () => {
7878
const credential = await completeEmailLinkSignIn(ui, window.location.href);
79-
8079
if (credential) {
8180
onSignIn?.(credential);
8281
}
8382
};
8483

8584
void completeSignIn();
86-
}, [ui, onSignIn]);
85+
// eslint-disable-next-line react-hooks/exhaustive-deps -- TODO(ehesp): ui triggers re-render
86+
}, [onSignIn]);
8787
}
8888

8989
export function EmailLinkAuthForm({ onEmailSent, onSignIn }: EmailLinkAuthFormProps) {

packages/shadcn/src/components/email-link-auth-form.test.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -227,12 +227,10 @@ describe("<EmailLinkAuthForm />", () => {
227227
</FirebaseUIProvider>
228228
);
229229

230-
await act(async () => {
231-
// Wait for the useEffect to complete
232-
await new Promise((resolve) => setTimeout(resolve, 0));
230+
await waitFor(() => {
231+
expect(completeEmailLinkSignInMock).toHaveBeenCalled();
233232
});
234233

235-
expect(completeEmailLinkSignInMock).toHaveBeenCalledWith(mockUI.get(), window.location.href);
236234
expect(onSignInMock).toHaveBeenCalledWith(mockCredential);
237235
});
238236
});

0 commit comments

Comments
 (0)