mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-04 16:14:07 +00:00
132 lines
5.8 KiB
TypeScript
132 lines
5.8 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { act, renderHook } from '@testing-library/react';
|
|
import type { DragEvent } from 'react';
|
|
import type { Email, Mailbox } from '@/lib/jmap/types';
|
|
import type { IJMAPClient } from '@/lib/jmap/client-interface';
|
|
import { useEmailStore } from '@/stores/email-store';
|
|
import { useAuthStore } from '@/stores/auth-store';
|
|
|
|
const drag = vi.hoisted(() => ({
|
|
isDragging: true,
|
|
sourceMailboxId: '' as string | null,
|
|
draggedEmails: [] as Email[],
|
|
endDrag: () => {},
|
|
markDropped: () => {},
|
|
}));
|
|
vi.mock('@/contexts/drag-drop-context', () => ({ useDragDropContext: () => drag }));
|
|
|
|
const { useMailboxDrop } = await import('../use-mailbox-drop');
|
|
|
|
// userb is logged in to a Stalwart 0.16.23 and a 1.0 server and is account `e`
|
|
// on both: a folder's account id can't say which login it belongs to, only
|
|
// the sidebar list it is shown in can. Both have their own Archive as `f`.
|
|
|
|
function mailbox(overrides: Partial<Mailbox>): Mailbox {
|
|
return {
|
|
id: 'f', name: 'Archive', role: 'archive', 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, accountId: 'e',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function email(overrides: Partial<Email>): Email {
|
|
return { id: 'x', threadId: 't', subject: 's', receivedAt: '2026-09-01T08:00:00Z', keywords: {}, mailboxIds: { a: true }, ...overrides } as Email;
|
|
}
|
|
|
|
function dropEvent(ids: string[]): DragEvent<HTMLDivElement> {
|
|
return {
|
|
preventDefault: () => {},
|
|
stopPropagation: () => {},
|
|
dataTransfer: { getData: () => JSON.stringify(ids) },
|
|
} as unknown as DragEvent<HTMLDivElement>;
|
|
}
|
|
|
|
describe('useMailboxDrop', () => {
|
|
const client016 = { getAccountId: () => 'e' } as unknown as IJMAPClient;
|
|
const client10 = { getAccountId: () => 'e' } as unknown as IJMAPClient;
|
|
let moveEmailsToMailbox: ReturnType<typeof vi.fn>;
|
|
let crossAccountMoveEmails: ReturnType<typeof vi.fn>;
|
|
|
|
beforeEach(() => {
|
|
moveEmailsToMailbox = vi.fn().mockResolvedValue(true);
|
|
crossAccountMoveEmails = vi.fn().mockResolvedValue(true);
|
|
useAuthStore.setState({
|
|
client: client016,
|
|
activeAccountId: 'userb@v016',
|
|
getClientForAccount: (id: string) => (id === 'userb@v016' ? client016 : id === 'userb@v10' ? client10 : undefined),
|
|
// The 1.0 login first: matching the folder's account id against each
|
|
// login's own used to pick it for the 0.16.23 login's folders too.
|
|
getAllConnectedClients: () => new Map([['userb@v10', client10], ['userb@v016', client016]]),
|
|
} as never);
|
|
useEmailStore.setState({
|
|
isUnifiedView: false,
|
|
selectedKeyword: null,
|
|
searchQuery: '',
|
|
viewingAccountId: null,
|
|
selectedMailbox: 'a',
|
|
mailboxes: [mailbox({ id: 'a', name: 'Inbox', role: 'inbox' }), mailbox({})],
|
|
selectedEmailKeys: new Set(),
|
|
moveEmailsToMailbox: moveEmailsToMailbox as never,
|
|
crossAccountMoveEmails: crossAccountMoveEmails as never,
|
|
refreshCurrentMailbox: vi.fn().mockResolvedValue(undefined) as never,
|
|
});
|
|
drag.sourceMailboxId = 'a';
|
|
drag.draggedEmails = [email({ id: 'm1' })];
|
|
});
|
|
|
|
const drop = async (target: Mailbox, ids: string[], accountId?: string) => {
|
|
const { result } = renderHook(() => useMailboxDrop({ mailbox: target, accountId, onSuccess: () => {}, onError: () => {} }));
|
|
await act(async () => { result.current.dropHandlers.onDrop(dropEvent(ids)); });
|
|
};
|
|
|
|
it('lets the store move a drop from the unified view, into the folder of the login it is listed under', async () => {
|
|
useEmailStore.setState({ isUnifiedView: true, selectedMailbox: '__unified_inbox' });
|
|
drag.sourceMailboxId = '__unified_inbox';
|
|
drag.draggedEmails = [
|
|
email({ id: 'm016', accountId: 'userb@v016', sourceClientAccountId: 'userb@v016', sourceAccountId: 'e' }),
|
|
email({ id: 't016', accountId: 'c', sourceClientAccountId: 'userb@v016', sourceAccountId: 'c' }),
|
|
];
|
|
|
|
await drop(mailbox({}), ['m016', 't016'], 'userb@v10');
|
|
await drop(mailbox({}), ['m016', 't016']);
|
|
|
|
expect(moveEmailsToMailbox.mock.calls).toEqual([
|
|
[client016, ['m016', 't016'], 'f', 'userb@v10', drag.draggedEmails],
|
|
[client016, ['m016', 't016'], 'f', 'userb@v016', drag.draggedEmails],
|
|
]);
|
|
expect(crossAccountMoveEmails).not.toHaveBeenCalled();
|
|
});
|
|
|
|
// The team's `x` and usera's own `x` are unrelated messages; one of them
|
|
// is dragged.
|
|
it('hands the store the dragged row, not only its id, in a list that mixes accounts', async () => {
|
|
useEmailStore.setState({ isUnifiedView: true, selectedMailbox: '__unified_inbox' });
|
|
drag.sourceMailboxId = '__unified_inbox';
|
|
const dragged = email({ id: 'x', sourceClientAccountId: 'userb@v016', sourceAccountId: 'c' });
|
|
drag.draggedEmails = [dragged];
|
|
|
|
await drop(mailbox({}), ['x']);
|
|
|
|
expect(moveEmailsToMailbox).toHaveBeenCalledWith(client016, ['x'], 'f', 'userb@v016', [dragged]);
|
|
});
|
|
|
|
it('moves a drop onto another login\'s folder across accounts, although both logins are account `e`', async () => {
|
|
useAuthStore.setState({
|
|
getAllConnectedClients: () => new Map([['userb@v016', client016], ['userb@v10', client10]]),
|
|
} as never);
|
|
|
|
await drop(mailbox({}), ['m1'], 'userb@v10');
|
|
|
|
expect(crossAccountMoveEmails).toHaveBeenCalledWith(new Map([['userb@v016', ['m1']]]), 'userb@v10', 'f', undefined, undefined);
|
|
expect(moveEmailsToMailbox).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('moves a drop onto the active login\'s own folder in place', async () => {
|
|
await drop(mailbox({}), ['m1']);
|
|
|
|
expect(moveEmailsToMailbox).toHaveBeenCalledWith(client016, ['m1'], 'f');
|
|
expect(crossAccountMoveEmails).not.toHaveBeenCalled();
|
|
});
|
|
});
|