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)
}
}
})()