fix(profile): avoid starting TOTP cooldowns after unmount

This commit is contained in:
chihao.ou
2026-09-22 21:42:54 +08:00
parent b0e32fe5be
commit 1ca54bcc25
3 changed files with 18 additions and 0 deletions
@@ -101,6 +101,7 @@ const methodLoading = ref(true)
const verificationMethod = ref<'email' | 'password'>('password')
const loading = ref(false)
const sendingCode = ref(false)
let disposed = false
const codeCooldown = ref(0)
const cooldownTimer = ref<ReturnType<typeof setInterval> | null>(null)
const form = ref({
@@ -132,6 +133,7 @@ const handleSendCode = async () => {
sendingCode.value = true
try {
await totpAPI.sendVerifyCode()
if (disposed) return
appStore.showSuccess(t('profile.totp.codeSent'))
// Start cooldown
codeCooldown.value = 60
@@ -180,6 +182,7 @@ onMounted(() => {
})
onUnmounted(() => {
disposed = true
if (cooldownTimer.value) {
clearInterval(cooldownTimer.value)
cooldownTimer.value = null
@@ -190,6 +190,7 @@ const methodLoading = ref(true)
const verificationMethod = ref<'email' | 'password'>('password')
const verifyForm = ref({ emailCode: '', password: '' })
const sendingCode = ref(false)
let disposed = false
const codeCooldown = ref(0)
const cooldownTimer = ref<ReturnType<typeof setInterval> | null>(null)
@@ -328,6 +329,7 @@ const handleSendCode = async () => {
sendingCode.value = true
try {
await totpAPI.sendVerifyCode()
if (disposed) return
appStore.showSuccess(t('profile.totp.codeSent'))
// Start cooldown
codeCooldown.value = 60
@@ -397,6 +399,7 @@ onMounted(() => {
})
onUnmounted(() => {
disposed = true
if (cooldownTimer.value) {
clearInterval(cooldownTimer.value)
cooldownTimer.value = null
@@ -79,6 +79,18 @@ describe('TOTP 弹窗定时器清理', () => {
clearIntervalSpy.mockRestore()
})
it.each([TotpSetupModal, TotpDisableDialog])('does not start a cooldown after unmounting during send', async (component) => {
let resolve!: (value: unknown) => void
mocks.sendVerifyCode.mockReturnValueOnce(new Promise(res => { resolve = res }))
const wrapper = mount(component)
await flushPromises()
await wrapper.findAll('button').find(b => b.text().includes('profile.totp.sendCode'))!.trigger('click')
wrapper.unmount()
resolve({ success: true }); await flushPromises()
expect(setIntervalSpy).not.toHaveBeenCalled()
expect(mocks.showSuccess).not.toHaveBeenCalled()
})
it('TotpSetupModal 卸载时清理倒计时定时器', async () => {
const wrapper = mount(TotpSetupModal)
await flushPromises()