mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-03 16:16:59 +00:00
feat: tell plugins when the selection or the open folder changes
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { useEmailStore } from '../email-store';
|
||||
import { clearAllHooks, emailHooks } from '@/lib/plugin-hooks';
|
||||
import type { Email, Mailbox } from '@/lib/jmap/types';
|
||||
|
||||
// onEmailSelectionChange and onMailboxChange follow the store state, so every
|
||||
// path that changes the selection or the open folder fires them once.
|
||||
|
||||
function makeEmail(id: string, overrides: Partial<Email> = {}): Email {
|
||||
return { id, threadId: `t-${id}`, keywords: {}, mailboxIds: { inbox: true }, ...overrides } as Email;
|
||||
}
|
||||
|
||||
const mailboxes = [
|
||||
{ id: 'inbox', name: 'Inbox', role: 'inbox' },
|
||||
{ id: 'projects', name: 'Projects' },
|
||||
] as Mailbox[];
|
||||
|
||||
beforeEach(() => {
|
||||
clearAllHooks();
|
||||
useEmailStore.setState({
|
||||
mailboxes,
|
||||
accountMailboxes: {},
|
||||
viewingAccountId: null,
|
||||
selectedMailbox: 'inbox',
|
||||
emails: [makeEmail('a'), makeEmail('b'), makeEmail('c', { sourceClientAccountId: 'x', sourceAccountId: 'y' })],
|
||||
selectedEmailKeys: new Set(),
|
||||
lastSelectedEmailKey: null,
|
||||
listRows: null,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => clearAllHooks());
|
||||
|
||||
describe('onEmailSelectionChange', () => {
|
||||
it('reports the selected ids each time the selection changes', () => {
|
||||
const changed = vi.fn();
|
||||
emailHooks.onEmailSelectionChange.register('t', changed);
|
||||
const store = useEmailStore.getState();
|
||||
|
||||
store.toggleEmailSelection(makeEmail('a'));
|
||||
store.selectRangeEmails(makeEmail('b'));
|
||||
store.selectAllEmails();
|
||||
store.clearSelection();
|
||||
// Clearing an empty selection is no change.
|
||||
store.clearSelection();
|
||||
|
||||
expect(changed.mock.calls).toEqual([
|
||||
[{ emailIds: ['a'] }],
|
||||
[{ emailIds: ['a', 'b'] }],
|
||||
[{ emailIds: ['a', 'b', 'c'] }],
|
||||
[{ emailIds: [] }],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onMailboxChange', () => {
|
||||
it('reports the folder opened, its role and the one left', () => {
|
||||
const changed = vi.fn();
|
||||
emailHooks.onMailboxChange.register('t', changed);
|
||||
|
||||
useEmailStore.getState().selectMailbox('projects');
|
||||
useEmailStore.getState().selectMailbox('projects');
|
||||
useEmailStore.getState().selectMailbox('inbox');
|
||||
|
||||
expect(changed.mock.calls).toEqual([
|
||||
[{ mailboxId: 'projects', role: null, previousMailboxId: 'inbox' }],
|
||||
[{ mailboxId: 'inbox', role: 'inbox', previousMailboxId: 'projects' }],
|
||||
]);
|
||||
});
|
||||
});
|
||||
+14
-1
@@ -7,7 +7,7 @@ import { useCalendarStore } from "@/stores/calendar-store";
|
||||
import { orderKeywords, placeHeldRows, type HeldKeywords, type SortLevel } from "@/lib/message-list-order";
|
||||
import { SearchFilters, DEFAULT_SEARCH_FILTERS, buildJMAPFilter, isFilterEmpty } from "@/lib/jmap/search-utils";
|
||||
import { emailHooks } from "@/lib/plugin-hooks";
|
||||
import { allowEmailMove, emitEmailMove, keywordChangeLists, mailboxToView, quotaToView } from "@/lib/plugin-mail-hooks";
|
||||
import { allowEmailMove, emitEmailMove, idsFromSelectionKeys, keywordChangeLists, mailboxToView, quotaToView } from "@/lib/plugin-mail-hooks";
|
||||
import { emailSummary } from "@/lib/plugin-projection";
|
||||
import { resolveThreadRoute } from "@/lib/thread-routing";
|
||||
import { emailKeyFor, threadKeyFor, threadIdFromKey, KEYWORD_PREFIX, KEYWORD_PREFIX_LEGACY } from "@/lib/thread-utils";
|
||||
@@ -6385,6 +6385,19 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
|
||||
// every path that changes it (clicks, keyboard, a batch action clearing the
|
||||
// selection, an account switch) fires them once.
|
||||
useEmailStore.subscribe((state, prev) => {
|
||||
if (state.selectedEmailKeys !== prev.selectedEmailKeys && emailHooks.onEmailSelectionChange.size > 0) {
|
||||
const changed = state.selectedEmailKeys.size !== prev.selectedEmailKeys.size
|
||||
|| [...state.selectedEmailKeys].some(key => !prev.selectedEmailKeys.has(key));
|
||||
if (changed) void emailHooks.onEmailSelectionChange.emit({ emailIds: idsFromSelectionKeys(state.selectedEmailKeys) });
|
||||
}
|
||||
if (state.selectedMailbox !== prev.selectedMailbox && state.selectedMailbox && emailHooks.onMailboxChange.size > 0) {
|
||||
const mailbox = mailboxesInView(state).find(mb => mb.id === state.selectedMailbox);
|
||||
void emailHooks.onMailboxChange.emit({
|
||||
mailboxId: state.selectedMailbox,
|
||||
role: mailbox?.role ?? (state.isUnifiedView ? state.unifiedRole : null) ?? null,
|
||||
previousMailboxId: prev.selectedMailbox || null,
|
||||
});
|
||||
}
|
||||
if (state.quota && state.quota !== prev.quota
|
||||
&& (state.quota.used !== prev.quota?.used || state.quota.total !== prev.quota?.total)) {
|
||||
void emailHooks.onQuotaChange.emit(quotaToView(state.quota));
|
||||
|
||||
Reference in New Issue
Block a user