From 4dc965aebf304ffd768d586e57aa8ae905ba842b Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Thu, 19 Mar 2026 11:25:15 +0800 Subject: [PATCH] tweaks --- .../components/__tests__/splash.spec.tsx | 123 ++++++++++++++++++ web/app/(shareLayout)/components/splash.tsx | 13 +- 2 files changed, 130 insertions(+), 6 deletions(-) create mode 100644 web/app/(shareLayout)/components/__tests__/splash.spec.tsx diff --git a/web/app/(shareLayout)/components/__tests__/splash.spec.tsx b/web/app/(shareLayout)/components/__tests__/splash.spec.tsx new file mode 100644 index 0000000000..df079fda86 --- /dev/null +++ b/web/app/(shareLayout)/components/__tests__/splash.spec.tsx @@ -0,0 +1,123 @@ +import { render, screen, waitFor } from '@testing-library/react' +import Splash from '../splash' + +const mockReplace = vi.fn() +const mockWebAppLoginStatus = vi.fn() +const mockFetchAccessToken = vi.fn() +const mockSetWebAppAccessToken = vi.fn() +const mockSetWebAppPassport = vi.fn() +const mockWebAppLogout = vi.fn() + +let mockShareCode: string | null = null +let mockEmbeddedUserId: string | null = null +let mockMessage: string | null = null +let mockRedirectUrl: string | null = '/chat/test-share-code' +let mockCode: string | null = null +let mockTokenFromUrl: string | null = null + +vi.mock('@/context/web-app-context', () => ({ + useWebAppStore: (selector: (state: { shareCode: string | null, embeddedUserId: string | null }) => unknown) => + selector({ + shareCode: mockShareCode, + embeddedUserId: mockEmbeddedUserId, + }), +})) + +vi.mock('@/next/navigation', () => ({ + useRouter: () => ({ + replace: mockReplace, + }), + useSearchParams: () => ({ + get: (key: string) => { + if (key === 'redirect_url') + return mockRedirectUrl + if (key === 'message') + return mockMessage + if (key === 'code') + return mockCode + if (key === 'web_sso_token') + return mockTokenFromUrl + return null + }, + toString: () => { + const params = new URLSearchParams() + if (mockRedirectUrl) + params.set('redirect_url', mockRedirectUrl) + if (mockMessage) + params.set('message', mockMessage) + if (mockCode) + params.set('code', mockCode) + if (mockTokenFromUrl) + params.set('web_sso_token', mockTokenFromUrl) + return params.toString() + }, + * [Symbol.iterator]() { + const params = new URLSearchParams(this.toString()) + yield* params.entries() + }, + }), +})) + +vi.mock('@/service/share', () => ({ + fetchAccessToken: (...args: unknown[]) => mockFetchAccessToken(...args), +})) + +vi.mock('@/service/webapp-auth', () => ({ + setWebAppAccessToken: (...args: unknown[]) => mockSetWebAppAccessToken(...args), + setWebAppPassport: (...args: unknown[]) => mockSetWebAppPassport(...args), + webAppLoginStatus: (...args: unknown[]) => mockWebAppLoginStatus(...args), + webAppLogout: (...args: unknown[]) => mockWebAppLogout(...args), +})) + +describe('Share Splash', () => { + beforeEach(() => { + vi.clearAllMocks() + mockShareCode = null + mockEmbeddedUserId = null + mockMessage = null + mockRedirectUrl = '/chat/test-share-code' + mockCode = null + mockTokenFromUrl = null + mockWebAppLoginStatus.mockResolvedValue({ + userLoggedIn: true, + appLoggedIn: true, + }) + mockFetchAccessToken.mockResolvedValue({ access_token: 'token' }) + }) + + describe('Share Code Guard', () => { + it('should skip login-status checks until the share code is available', async () => { + render( + +
share child
+
, + ) + + expect(screen.getByText('share child')).toBeInTheDocument() + await waitFor(() => { + expect(mockWebAppLoginStatus).not.toHaveBeenCalled() + }) + expect(mockFetchAccessToken).not.toHaveBeenCalled() + }) + + it('should resume the auth flow after the share code becomes available', async () => { + const { rerender } = render( + +
share child
+
, + ) + + mockShareCode = 'share-code' + rerender( + +
share child
+
, + ) + + await waitFor(() => { + expect(mockWebAppLoginStatus).toHaveBeenCalledWith('share-code', undefined) + }) + expect(mockReplace).toHaveBeenCalledWith('/chat/test-share-code') + }) + }) +}) diff --git a/web/app/(shareLayout)/components/splash.tsx b/web/app/(shareLayout)/components/splash.tsx index 192a5a90e6..267fae4338 100644 --- a/web/app/(shareLayout)/components/splash.tsx +++ b/web/app/(shareLayout)/components/splash.tsx @@ -26,12 +26,13 @@ const Splash: FC = ({ children }) => { }, [searchParams]) const backToHome = useCallback(async () => { - await webAppLogout(shareCode!) + if (shareCode) + await webAppLogout(shareCode) const url = getSigninUrl() router.replace(url) }, [getSigninUrl, router, shareCode]) useEffect(() => { - if (message) + if (message || !shareCode) return if (tokenFromUrl) @@ -44,7 +45,7 @@ const Splash: FC = ({ children }) => { (async () => { // if access mode is public, user login is always true, but the app login(passport) may be expired - const { userLoggedIn, appLoggedIn } = await webAppLoginStatus(shareCode!, embeddedUserId || undefined) + const { userLoggedIn, appLoggedIn } = await webAppLoginStatus(shareCode, embeddedUserId || undefined) if (userLoggedIn && appLoggedIn) { redirectOrFinish() } @@ -54,14 +55,14 @@ const Splash: FC = ({ children }) => { else if (userLoggedIn && !appLoggedIn) { try { const { access_token } = await fetchAccessToken({ - appCode: shareCode!, + appCode: shareCode, userId: embeddedUserId || undefined, }) - setWebAppPassport(shareCode!, access_token) + setWebAppPassport(shareCode, access_token) redirectOrFinish() } catch { - await webAppLogout(shareCode!) + await webAppLogout(shareCode) } } })()