Files
SRCmail/stores/__tests__/email-store-unified-actions.test.ts
T

128 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { useEmailStore } from '../email-store';
import { useAuthStore } from '../auth-store';
import type { Email, Mailbox } from '@/lib/jmap/types';
import type { IJMAPClient } from '@/lib/jmap/client-interface';
// Regression coverage for issue #281: single-email actions performed in the
// unified inbox must be routed to the *email's own account* client, not the
// active account's. Sending them to the active account silently no-ops
// server-side (JMAP returns notUpdated without throwing), so the change is lost
// on the next reload.
function makeMailbox(overrides: Partial<Mailbox> = {}): Mailbox {
return {
id: 'inbox',
name: 'Inbox',
sortOrder: 0,
totalEmails: 0,
unreadEmails: 0,
totalThreads: 0,
unreadThreads: 0,
myRights: {
mayReadItems: true,
mayAddItems: true,
mayRemoveItems: true,
maySetSeen: true,
maySetKeywords: true,
mayCreateChild: true,
mayRename: true,
mayDelete: true,
maySubmit: true,
},
isSubscribed: true,
isShared: false,
...overrides,
};
}
function makeEmail(overrides: Partial<Email> = {}): Email {
return {
id: 'email-1',
threadId: 'thread-1',
subject: 'Hi',
receivedAt: new Date().toISOString(),
keywords: {},
mailboxIds: {},
...overrides,
} as Email;
}
function makeClient() {
return {
markAsRead: vi.fn().mockResolvedValue(undefined),
toggleStar: vi.fn().mockResolvedValue(undefined),
moveEmail: vi.fn().mockResolvedValue(undefined),
} as unknown as IJMAPClient;
}
describe('unified-view single-email action routing (#281)', () => {
let activeClient: IJMAPClient; // account-a, also the "passed" client
let accountBClient: IJMAPClient;
beforeEach(() => {
activeClient = makeClient();
accountBClient = makeClient();
// Route account-b to its own client; account-a falls back to the active one.
useAuthStore.setState({
getClientForAccount: (id: string) =>
(id === 'account-b' ? accountBClient : undefined) as never,
} as never);
useEmailStore.setState({
isUnifiedView: true,
unifiedRole: 'inbox',
viewingAccountId: null,
selectedMailbox: '',
mailboxes: [makeMailbox({ id: 'a-inbox', role: 'inbox' })],
accountMailboxes: {
'account-a': [makeMailbox({ id: 'a-inbox', role: 'inbox' })],
'account-b': [
makeMailbox({ id: 'b-inbox', role: 'inbox' }),
makeMailbox({ id: 'b-archive', name: 'Archive', role: 'archive' }),
],
},
processingReadStatus: new Set(),
selectedEmail: null,
selectedEmailIds: new Set(),
emails: [
makeEmail({ id: 'email-b', accountId: 'account-b', keywords: {}, mailboxIds: { 'b-inbox': true } }),
],
});
});
it('routes markAsRead to the emails account client', async () => {
await useEmailStore.getState().markAsRead(activeClient, 'email-b', true);
expect(accountBClient.markAsRead).toHaveBeenCalledWith('email-b', true, undefined);
expect(activeClient.markAsRead).not.toHaveBeenCalled();
});
it('routes toggleStar to the emails account client', async () => {
await useEmailStore.getState().toggleStar(activeClient, 'email-b');
expect(accountBClient.toggleStar).toHaveBeenCalledWith('email-b', true);
expect(activeClient.toggleStar).not.toHaveBeenCalled();
});
it('routes moveToMailbox to the emails account client with that accounts destination', async () => {
await useEmailStore.getState().moveToMailbox(activeClient, 'email-b', 'b-archive');
expect(accountBClient.moveEmail).toHaveBeenCalledWith('email-b', 'b-archive', undefined);
expect(activeClient.moveEmail).not.toHaveBeenCalled();
});
it('still uses the active/passed client outside unified view', async () => {
useEmailStore.setState({
isUnifiedView: false,
emails: [makeEmail({ id: 'email-a', accountId: 'account-a', mailboxIds: { 'a-inbox': true } })],
});
await useEmailStore.getState().markAsRead(activeClient, 'email-a', true);
expect(activeClient.markAsRead).toHaveBeenCalledWith('email-a', true, undefined);
expect(accountBClient.markAsRead).not.toHaveBeenCalled();
});
});