Skip to content

Commit 52eacea

Browse files
committed
test: replace guard-pattern test with NEVER-observable regression test
The guard-pattern test passed on main because SuspenseSubject's warmup subscription made onAuthStateChanged fire before getSnapshot() was called. The new test installs NEVER as the auth:user observable so it can never emit, isolating the synchronous-user guarantee to the initialData seeding path only. Fails on main (status:'loading'), passes with the fix (status:'success').
1 parent 9b03db5 commit 52eacea

1 file changed

Lines changed: 20 additions & 20 deletions

File tree

test/auth.test.tsx

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { cleanup, render, waitFor, renderHook, act } from '@testing-library/react';
22
import '@testing-library/jest-dom/extend-expect';
33
import * as React from 'react';
4+
import { NEVER } from 'rxjs';
5+
import { preloadObservable } from '../src/useObservable';
46
import {
57
FirebaseAppProvider,
68
AuthCheck,
@@ -326,39 +328,37 @@ describe('Authentication', () => {
326328
expect(result.current.data).toEqual(getAuth(app).currentUser);
327329
});
328330

329-
it('synchronously returns a user when mounted as a child of a useSigninCheck guard', async () => {
331+
it('synchronously returns the current user without waiting for the observable', async () => {
330332
await act(async () => {
331333
await signIn();
332334
});
333335

334-
// Capture the very first render of the child — before any re-renders
336+
// Replace the auth:user observable with NEVER so it never emits.
337+
// Without the fix (no initialData seeding), status is 'loading' on first render.
338+
// With the fix (initialData = auth.currentUser), status is 'success' synchronously.
339+
const cache = (globalThis as any)._reactFirePreloadedObservables as Map<string, any>;
340+
const authUserKey = `auth:user:${getAuth(app).name}`;
341+
cache?.delete(authUserKey);
342+
preloadObservable(NEVER, authUserKey);
343+
335344
let capturedFirstRender: { user: any; status: string } | undefined;
336345

337-
const UserChild = () => {
346+
const UserComponent = () => {
338347
const { data: user, status } = useUser();
339348
if (capturedFirstRender === undefined) {
340349
capturedFirstRender = { user, status };
341350
}
342-
return <span data-testid="child-user">{user?.email ?? 'no user'}</span>;
343-
};
344-
345-
const Guard = () => {
346-
const { data: signinResult, status } = useSigninCheck();
347-
if (status !== 'success' || !signinResult?.signedIn) {
348-
return <span data-testid="guard-loading">loading</span>;
349-
}
350-
return <UserChild />;
351+
return <span data-testid="user-output">{String(status)}</span>;
351352
};
352353

353-
const { findByTestId } = render(<Guard />, { wrapper: Provider });
354-
355-
await findByTestId('child-user');
354+
try {
355+
render(<UserComponent />, { wrapper: Provider });
356356

357-
// On main (without the fix), useUser mounts with a fresh SuspenseSubject
358-
// (different observableId from useSigninCheck) and returns status:'loading', data:undefined.
359-
// With the fix, currentUser seeds initialData so the first render is already success.
360-
expect(capturedFirstRender!.status).toBe('success');
361-
expect(capturedFirstRender!.user).toEqual(getAuth(app).currentUser);
357+
expect(capturedFirstRender!.status).toBe('success');
358+
expect(capturedFirstRender!.user).toEqual(getAuth(app).currentUser);
359+
} finally {
360+
cache?.delete(authUserKey);
361+
}
362362
});
363363

364364
it('does not show a logged-out user after navigating away', async () => {

0 commit comments

Comments
 (0)