fix: mark a message read when the notification's Delete moves it to Trash and the delete action says so #86

This commit is contained in:
Linus Rath
2026-10-29 02:42:00 +01:00
parent 77327ea856
commit 65f00640ba
2 changed files with 52 additions and 1 deletions
@@ -288,6 +288,40 @@ describe('pushBackgroundTask notifications', () => {
]);
});
it.each([
['trash-and-read', { mailboxIds: { 'trash-box': true }, 'keywords/$seen': true }],
['trash', { mailboxIds: { 'trash-box': true } }],
// Never destroyed from a notification (#68), but not marked read either.
['permanent', { mailboxIds: { 'trash-box': true } }],
])('follows the delete action setting "%s" (#86)', async (deleteAction, patch) => {
await AsyncStorage.setItem('webmail:settings:v1', JSON.stringify({ deleteAction }));
const postCalls: any[] = [];
(secureFetch as ReturnType<typeof vi.fn>).mockImplementation(async (url: string, opts?: any) => {
if (opts?.body) postCalls.push(JSON.parse(opts.body));
return {
ok: true,
json: async () => (url.endsWith('/.well-known/jmap')
? {
apiUrl: 'https://mail.example.com/jmap/',
primaryAccounts: { 'urn:ietf:params:jmap:mail': 'jmap-primary' },
accounts: { 'jmap-primary': {} },
}
: {
methodResponses: [
['Mailbox/get', { list: [{ id: 'trash-box', role: 'trash' }] }, '0'],
['Email/set', { updated: { m1: {} } }, '0'],
],
}),
};
});
await handleNotificationAction({ action: 'delete', emailId: 'm1', accountId: LOCAL });
expect(postCalls[1].methodCalls).toEqual([
['Email/set', { accountId: 'jmap-primary', update: { m1: patch } }, '0'],
]);
});
it.each([
['has no trash mailbox', ['Mailbox/get', { list: [{ id: 'inbox', role: 'inbox' }] }, '0']],
['cannot list mailboxes', ['error', { type: 'accountNotFound' }, '0']],
+18 -1
View File
@@ -51,6 +51,7 @@ const EMAIL_PROPERTIES = [
interface PushPersistedSettings {
emailNotificationsEnabled?: boolean;
senderFavicons?: boolean;
deleteAction?: string;
}
async function emailNotificationsAllowed(): Promise<boolean> {
@@ -78,6 +79,20 @@ export async function senderFaviconsAllowed(): Promise<boolean> {
}
}
// The "Delete Action" setting, as far as a notification's Delete honours it:
// "Move to Trash and mark as read" marks the message read on the way (#86).
// "Delete Permanently" still only moves to Trash: a notification never
// destroys mail (#68).
async function notificationDeleteMarksRead(): Promise<boolean> {
try {
const raw = await AsyncStorage.getItem(SETTINGS_STORAGE_KEY);
if (!raw) return false;
return (JSON.parse(raw) as PushPersistedSettings).deleteAction === 'trash-and-read';
} catch {
return false;
}
}
/** The app's language: the in-app override, else the device's. */
export async function notificationLocale(): Promise<LocaleCode> {
try {
@@ -665,13 +680,15 @@ export async function handleNotificationAction(data: unknown): Promise<void> {
console.warn('[push] no trash mailbox for notification delete', emailAccountId);
return;
}
const patch: Record<string, unknown> = { mailboxIds: { [trash.id]: true } };
if (await notificationDeleteMarksRead()) patch['keywords/$seen'] = true;
const res = await jmapPost(session, [
[
'Email/set',
{
accountId: emailAccountId,
update: {
[emailId]: { mailboxIds: { [trash.id]: true } },
[emailId]: patch,
},
},
'0',