From 654c4f69789f02d08e926a77338f1b94f34f8658 Mon Sep 17 00:00:00 2001 From: elky Date: Wed, 12 Aug 2026 16:55:34 +0800 Subject: [PATCH] fix(auth): preserve turnstile while typing email --- .../auth/components/RegisterDialog.vue | 2 +- .../RegisterDialog.turnstile.spec.ts | 62 +++++++++++++++++-- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/frontend/src/features/auth/components/RegisterDialog.vue b/frontend/src/features/auth/components/RegisterDialog.vue index c97a8cc02..d674410d0 100644 --- a/frontend/src/features/auth/components/RegisterDialog.vue +++ b/frontend/src/features/auth/components/RegisterDialog.vue @@ -683,7 +683,7 @@ watch( cooldownTimer.value = null } codeDigits.value = ['', '', '', '', '', ''] - resetTurnstile() + // Turnstile validates the action, not the email value. Keep its challenge stable while typing. } // 清除之前的定时器 diff --git a/frontend/src/features/auth/components/__tests__/RegisterDialog.turnstile.spec.ts b/frontend/src/features/auth/components/__tests__/RegisterDialog.turnstile.spec.ts index 0426c7ad5..88001abbd 100644 --- a/frontend/src/features/auth/components/__tests__/RegisterDialog.turnstile.spec.ts +++ b/frontend/src/features/auth/components/__tests__/RegisterDialog.turnstile.spec.ts @@ -2,8 +2,9 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { createApp, nextTick } from 'vue' import RegisterDialog from '../RegisterDialog.vue' -const { registerMock, toastErrorMock, toastSuccessMock } = vi.hoisted(() => ({ +const { registerMock, sendVerificationCodeMock, toastErrorMock, toastSuccessMock } = vi.hoisted(() => ({ registerMock: vi.fn(), + sendVerificationCodeMock: vi.fn(), toastErrorMock: vi.fn(), toastSuccessMock: vi.fn(), })) @@ -11,7 +12,7 @@ const { registerMock, toastErrorMock, toastSuccessMock } = vi.hoisted(() => ({ vi.mock('@/api/auth', () => ({ authApi: { register: registerMock, - sendVerificationCode: vi.fn(), + sendVerificationCode: sendVerificationCodeMock, verifyEmail: vi.fn(), getVerificationStatus: vi.fn(), }, @@ -75,13 +76,19 @@ async function settle() { } } -async function mountRegisterDialog() { +async function mountRegisterDialog({ + emailConfigured = false, + requireEmailVerification = false, +}: { + emailConfigured?: boolean + requireEmailVerification?: boolean +} = {}) { const root = document.createElement('div') document.body.appendChild(root) const app = createApp(RegisterDialog, { open: true, - emailConfigured: false, - requireEmailVerification: false, + emailConfigured, + requireEmailVerification, passwordPolicyLevel: 'weak', turnstileEnabled: true, turnstileSiteKey: 'site-public-key', @@ -135,6 +142,12 @@ describe('RegisterDialog Turnstile flow', () => { beforeEach(() => { registerMock.mockReset() registerMock.mockResolvedValue({ message: '注册成功' }) + sendVerificationCodeMock.mockReset() + sendVerificationCodeMock.mockResolvedValue({ + success: true, + message: 'ok', + expire_minutes: 5, + }) toastErrorMock.mockReset() toastSuccessMock.mockReset() }) @@ -188,4 +201,43 @@ describe('RegisterDialog Turnstile flow', () => { expect(registerMock).not.toHaveBeenCalled() expect(toastErrorMock).toHaveBeenCalledWith('人机验证加载失败,请重试', '人机验证') }) + + it('does not reset Turnstile while the user types an email address', async () => { + const turnstile = installTurnstileMock() + mounted = await mountRegisterDialog({ + emailConfigured: true, + requireEmailVerification: true, + }) + await settle() + + turnstile.succeed('turnstile-token') + await settle() + + const emailInput = document.body.querySelector('#reg-email') as HTMLInputElement | null + expect(emailInput).toBeTruthy() + + let email = '' + for (const character of 'alice@example.com') { + email += character + emailInput!.value = email + emailInput!.dispatchEvent(new Event('input', { bubbles: true })) + await nextTick() + } + await settle() + + expect(turnstile.render).toHaveBeenCalledTimes(1) + expect(turnstile.reset).not.toHaveBeenCalled() + const sendCodeButton = Array.from(document.body.querySelectorAll('button')).find( + (button) => button.textContent?.trim() === '发送验证码' + ) + expect(sendCodeButton?.hasAttribute('disabled')).toBe(false) + sendCodeButton!.click() + await settle() + + expect(sendVerificationCodeMock).toHaveBeenCalledWith( + 'alice@example.com', + 'turnstile-token' + ) + expect(turnstile.reset).toHaveBeenCalledWith('widget-id') + }) })