fix(profile): ignore obsolete notification email code responses

This commit is contained in:
chihao.ou
2026-09-30 02:20:42 +08:00
parent a60a29549f
commit 636faa695b
2 changed files with 37 additions and 0 deletions
@@ -203,6 +203,7 @@ const verifyingSaved = ref(false)
const sendingSavedCode = ref(false)
const verifyCountdown = ref(0)
let verifyTimer: ReturnType<typeof setInterval> | null = null
let disposed = false
const canAddMore = computed(() => {
return emailEntries.value.length + pendingEmails.value.length < maxTotalEmails
@@ -220,6 +221,7 @@ onMounted(() => {
})
onUnmounted(() => {
disposed = true
for (const pe of pendingEmails.value) {
if (pe.timer) clearInterval(pe.timer)
}
@@ -281,6 +283,7 @@ async function sendCodeFor(idx: number) {
pe.sending = true
try {
await userAPI.sendNotifyEmailCode(pe.email)
if (disposed || !pendingEmails.value.includes(pe)) return
pe.codeSent = true
pe.countdown = 60
pe.timer = setInterval(() => {
@@ -334,6 +337,7 @@ async function sendCodeForSaved(email: string) {
sendingSavedCode.value = true
try {
await userAPI.sendNotifyEmailCode(email)
if (disposed) return
verifyingEmail.value = email
verifyCode.value = ''
verifyCountdown.value = 60
@@ -44,6 +44,39 @@ describe('ProfileBalanceNotifyCard', () => {
vi.useRealTimers()
})
it.each(['remove', 'unmount'])('does not start a pending email timer after %s', async (action) => {
const request = deferred()
sendNotifyEmailCode.mockReturnValueOnce(request.promise)
const wrapper = mount(ProfileBalanceNotifyCard, {
props: { enabled: true, threshold: null, extraEmails: [], systemDefaultThreshold: 5, userEmail: '' }
})
await wrapper.get('input[type="email"]').setValue('new@example.com')
await button(wrapper, 'common.add').trigger('click')
await button(wrapper, 'profile.balanceNotify.sendCode').trigger('click')
if (action === 'remove') await button(wrapper, 'profile.balanceNotify.removeEmail').trigger('click')
else wrapper.unmount()
request.resolve()
await flushPromises()
expect(vi.getTimerCount()).toBe(0)
})
it('does not start a saved email timer after unmount', async () => {
const request = deferred()
sendNotifyEmailCode.mockReturnValueOnce(request.promise)
const wrapper = mount(ProfileBalanceNotifyCard, {
props: {
enabled: true, threshold: null, systemDefaultThreshold: 5, userEmail: '',
extraEmails: [{ email: 'saved@example.com', disabled: false, verified: false }]
}
})
await button(wrapper, 'profile.balanceNotify.verify').trigger('click')
wrapper.unmount()
request.resolve()
await flushPromises()
expect(vi.getTimerCount()).toBe(0)
})
it.each([0, 1])('removes only verified emails when request %i finishes first', async (first) => {
const emails = ['first@example.com', 'second@example.com', 'third@example.com']
const requests = [deferred(), deferred()]