diff --git a/components/pro/__tests__/pro-compose-tab-source.test.tsx b/components/pro/__tests__/pro-compose-tab-source.test.tsx index 24f08f8d3..c4cf191c9 100644 --- a/components/pro/__tests__/pro-compose-tab-source.test.tsx +++ b/components/pro/__tests__/pro-compose-tab-source.test.tsx @@ -6,7 +6,7 @@ import React from 'react'; // login and account they are in. Email ids repeat across accounts and // servers, so the active login's client would act on its own namesake. -const { activeClient, otherClient, markEmailKeyword, toastError } = vi.hoisted(() => { +const { activeClient, otherClient, markEmailKeyword, toastError, sendResult } = vi.hoisted(() => { const makeClient = (label: string) => ({ label, getAccountId: () => `jmap-${label}`, @@ -18,6 +18,7 @@ const { activeClient, otherClient, markEmailKeyword, toastError } = vi.hoisted(( otherClient: makeClient('account-2'), markEmailKeyword: vi.fn(async () => {}), toastError: vi.fn(), + sendResult: { scheduled: false }, }; }); @@ -51,7 +52,7 @@ vi.mock('@/stores/auth-store', () => { }); vi.mock('@/stores/email-store', () => { const state = { - sendEmail: async () => ({ scheduled: false }), + sendEmail: async () => ({ ...sendResult }), markEmailKeyword, refreshCurrentMailbox: async () => {}, fetchScheduledEmails: async () => {}, @@ -77,6 +78,7 @@ import { ProComposeTabBody } from '../pro-compose-tab-body'; beforeEach(() => { markEmailKeyword.mockClear(); + sendResult.scheduled = false; toastError.mockClear(); activeClient.deleteEmail.mockClear(); otherClient.deleteEmail.mockClear(); @@ -94,6 +96,19 @@ describe('Pro compose tab', () => { expect(markEmailKeyword).toHaveBeenCalledWith(activeClient, 'eaaaaab', '$answered', source); }); + // A send delay (undo send) makes every send a held submission, so the + // flag has to be set before the held send returns, as in the inline composer (#985). + it.each([['reply', '$answered'], ['forward', '$forwarded']] as const)('flags the source of a held %s', async (mode, keyword) => { + sendResult.scheduled = true; + const source = { clientAccountId: 'local-2', accountId: 'jmap-group' }; + render(); + + fireEvent.click(screen.getByText('send')); + + await waitFor(() => expect(markEmailKeyword).toHaveBeenCalled()); + expect(markEmailKeyword).toHaveBeenCalledWith(activeClient, 'eaaaaab', keyword, source); + }); + it('discards the draft through the login that saved it', async () => { render(); diff --git a/components/pro/pro-compose-tab-body.tsx b/components/pro/pro-compose-tab-body.tsx index a30460dce..a3f1c72ce 100644 --- a/components/pro/pro-compose-tab-body.tsx +++ b/components/pro/pro-compose-tab-body.tsx @@ -100,13 +100,10 @@ export function ProComposeTabBody({ tabId, data }: ProComposeTabBodyProps) { } announceSent(result, sendData); - if (result.scheduled) { - await handleScheduledSendCreated(); - return; - } - // Mark the original message as $answered / $forwarded so the standard // viewer and list reflect the action (same behaviour as inline compose). + // Before the scheduled return: with a send delay every send is a held + // submission, and the flag was never set. (#985) if (data.sourceEmailId && (data.mode === 'reply' || data.mode === 'replyAll')) { try { await useEmailStore.getState().markEmailKeyword(client, data.sourceEmailId, '$answered', data.sourceEmailSource ?? undefined); @@ -121,6 +118,11 @@ export function ProComposeTabBody({ tabId, data }: ProComposeTabBodyProps) { } } + if (result.scheduled) { + await handleScheduledSendCreated(); + return; + } + // Refresh the currently-active mail list so the new sent message / // updated keyword status shows up. await refreshCurrentMailbox(client);