diff --git a/components/email/__tests__/composer-plugin-edit.test.tsx b/components/email/__tests__/composer-plugin-edit.test.tsx index 85e47c0bb..950bb5987 100644 --- a/components/email/__tests__/composer-plugin-edit.test.tsx +++ b/components/email/__tests__/composer-plugin-edit.test.tsx @@ -112,8 +112,7 @@ vi.mock('@/stores/settings-store', () => { typeof sel === 'function' ? sel(state) : state; hook.getState = () => state; hook.setState = (p: Partial) => Object.assign(state, p); - // The calendar store (reached through the assistant's host calls) registers its sync bridge on import. - return { useSettingsStore: hook, registerCalendarSubscriptionSyncBridge: () => {} }; + return { useSettingsStore: hook }; }); vi.mock('@/stores/contact-store', () => { @@ -406,27 +405,4 @@ describe('plugins editing the message being written', () => { cleanup(); expect(composerHandle(id)).toBeNull(); }); - - // api.files.attach: a file from the user's Files keeps the login that - // holds it, so a draft written from another login copies it over before - // it is sent (rehomeForeignBlobs) and reads go to its owner. - it('attaches a Files file with the login that holds it', async () => { - const fetchB = vi.fn(async () => ({ type: 'text/plain', size: 1, arrayBuffer: async () => new Uint8Array([7]).buffer })); - const previous = useAuthStore.getState(); - const oldGetClient = previous.getClientForAccount; - useAuthStore.setState({ getClientForAccount: (id: string) => (id === 'login-b' ? { fetchBlob: fetchB } : null) as never }); - try { - const { handle } = await renderComposer(); - let ok = false; - await act(async () => { ok = await handle.attachFile!({ blobId: 'F1', name: 'notes.md', type: 'text/markdown', size: 1, sourceAccountId: 'login-b' }); }); - expect(ok).toBe(true); - expect(await screen.findByText('notes.md')).toBeTruthy(); - const [attached] = handle.readMetadata!().attachments; - await handle.readAttachment!(attached.id); - expect(fetchB).toHaveBeenCalledWith('F1', 'notes.md', 'text/markdown'); - } finally { - cleanup(); - useAuthStore.setState({ getClientForAccount: oldGetClient }); - } - }); }); diff --git a/components/email/__tests__/composer-plugin-lifecycle.test.tsx b/components/email/__tests__/composer-plugin-lifecycle.test.tsx index c17e86a7b..ff7c7a0c0 100644 --- a/components/email/__tests__/composer-plugin-lifecycle.test.tsx +++ b/components/email/__tests__/composer-plugin-lifecycle.test.tsx @@ -118,8 +118,7 @@ vi.mock('@/stores/settings-store', () => { typeof sel === 'function' ? sel(state) : state; hook.getState = () => state; hook.setState = (p: Partial) => Object.assign(state, p); - // The calendar store (reached through the assistant's host calls) registers its sync bridge on import. - return { useSettingsStore: hook, registerCalendarSubscriptionSyncBridge: () => {} }; + return { useSettingsStore: hook }; }); vi.mock('@/stores/contact-store', () => { diff --git a/components/email/__tests__/composer-plugin-send.test.tsx b/components/email/__tests__/composer-plugin-send.test.tsx index 1e3479125..b95a9f450 100644 --- a/components/email/__tests__/composer-plugin-send.test.tsx +++ b/components/email/__tests__/composer-plugin-send.test.tsx @@ -110,8 +110,7 @@ vi.mock('@/stores/settings-store', () => { typeof sel === 'function' ? sel(state) : state; hook.getState = () => state; hook.setState = (p: Partial) => Object.assign(state, p); - // The calendar store (reached through the assistant's host calls) registers its sync bridge on import. - return { useSettingsStore: hook, registerCalendarSubscriptionSyncBridge: () => {} }; + return { useSettingsStore: hook }; }); vi.mock('@/stores/contact-store', () => { diff --git a/components/email/__tests__/composer-plugin-surfaces.test.tsx b/components/email/__tests__/composer-plugin-surfaces.test.tsx index 74f5f1eae..c9812f7c9 100644 --- a/components/email/__tests__/composer-plugin-surfaces.test.tsx +++ b/components/email/__tests__/composer-plugin-surfaces.test.tsx @@ -109,8 +109,7 @@ vi.mock('@/stores/settings-store', () => { typeof sel === 'function' ? sel(state) : state; hook.getState = () => state; hook.setState = (p: Partial) => Object.assign(state, p); - // The calendar store (reached through the assistant's host calls) registers its sync bridge on import. - return { useSettingsStore: hook, registerCalendarSubscriptionSyncBridge: () => {} }; + return { useSettingsStore: hook }; }); vi.mock('@/stores/contact-store', () => { @@ -681,36 +680,6 @@ describe('composer-sidebar and composer-sidebar-right', () => { } }); - it('show and hide the panels from the header, the left one at its start and the right one at its end', () => { - stubWidth(1400); - offerSlots('p1', ['composer-sidebar', 'composer-sidebar-right'], { startFolded: ['composer-sidebar-right'] }); - render(); - const header = document.querySelector('[data-composer-header]') as HTMLElement; - // As high as the reader's toolbar and the panels' own headers. - expect(header.className).toContain('md:h-14'); - const left = header.querySelector('[data-composer-panel-toggle="left"]') as HTMLElement; - const right = header.querySelector('[data-composer-panel-toggle="right"]') as HTMLElement; - expect(left).toBeTruthy(); - expect(right).toBeTruthy(); - // Left first; right just before Close, at the end. - expect(left.compareDocumentPosition(right) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy(); - expect(right.nextElementSibling).toBe(screen.getByTestId('composer-close')); - // The panels hold no buttons of their own: no strip, no row above the plugin. - expect(panel('right').dataset.composerSidebarMode).toBe('folded'); - expect(panel('right').hidden).toBe(true); - expect(panel('left').querySelector('button')).toBeNull(); - expect(right.getAttribute('aria-pressed')).toBe('false'); - - // Opening by hand asks the panel to take the focus, as a plugin's request does. - fireEvent.click(right); - expect(panel('right').dataset.composerSidebarMode).toBe('docked'); - expect(slotProps(screen.getByTestId('slot-composer-sidebar-right')).focusRequest).toBe(1); - expect(right.getAttribute('aria-pressed')).toBe('true'); - expect(right.getAttribute('aria-label')).toBe('plugin_panel_hide'); - fireEvent.click(right); - expect(panel('right').dataset.composerSidebarMode).toBe('folded'); - }); - it('open a folded panel over the composer when there is no room beside it', () => { stubWidth(600); offerSlots('p1', ['composer-sidebar-right']); diff --git a/lib/plugin-sandbox/assistant/c.ts b/lib/plugin-sandbox/assistant/c.ts index 410222978..d3b5c8347 100644 --- a/lib/plugin-sandbox/assistant/c.ts +++ b/lib/plugin-sandbox/assistant/c.ts @@ -15,10 +15,8 @@ import { doVacationGet, doVacationSet } from '../host-api-settings'; import { isPlainRecord, sessionClient } from '../host-api-input'; import { NOT_MINE } from './not-mine'; import { pluginHasPermission } from '../permissions'; -import { doCalendarDeleteWithUndo, doCalendarRespond, doCalendarUndoDelete } from './c.calendar'; import { doFiltersCreate, doFiltersList, doFiltersUndo } from './c.filters'; import { doEmailBatch, doEmailUndoBatch } from './c.batch'; -import { doDescribeSettings, doSetSettings, doUndoSettings } from './c.settings'; import type { AreaHostFeaturesC, VacationForwardField } from './c.types'; /** Method → permission, joined into PERM_PER_METHOD. */ @@ -75,12 +73,18 @@ async function doVacationSetGuarded(input: unknown): Promise import('./c.calendar'); +const settings = () => import('./c.settings'); + /** Runs a method of this area; NOT_MINE for any other. */ export async function dispatch(plugin: InstalledPlugin, method: string, args: unknown[]): Promise { switch (method) { - case 'calendar.respond': return doCalendarRespond(args[0], args[1]); - case 'calendar.deleteEvent': return doCalendarDeleteWithUndo(plugin.id, args[0]); - case 'calendar.undoDelete': return doCalendarUndoDelete(plugin.id, args[0]); + case 'calendar.respond': return (await calendar()).doCalendarRespond(args[0], args[1]); + case 'calendar.deleteEvent': return (await calendar()).doCalendarDeleteWithUndo(plugin.id, args[0]); + case 'calendar.undoDelete': return (await calendar()).doCalendarUndoDelete(plugin.id, args[0]); case 'filters.list': return doFiltersList(); case 'filters.create': return doFiltersCreate(plugin.id, args[0]); case 'filters.undo': return doFiltersUndo(plugin.id, args[0]); @@ -88,9 +92,9 @@ export async function dispatch(plugin: InstalledPlugin, method: string, args: un case 'email.undoBatch': return doEmailUndoBatch(plugin.id, args[0]); case 'vacation.get': return doVacationGetWithForward(); case 'vacation.set': return doVacationSetGuarded(args[0]); - case 'app.describeSettings': return doDescribeSettings(args[0]); - case 'app.setSettings': return doSetSettings(plugin.id, args[0]); - case 'app.undoSettings': return doUndoSettings(plugin.id, args[0]); + case 'app.describeSettings': return (await settings()).doDescribeSettings(args[0]); + case 'app.setSettings': return (await settings()).doSetSettings(plugin.id, args[0]); + case 'app.undoSettings': return (await settings()).doUndoSettings(plugin.id, args[0]); default: return NOT_MINE; } }