mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 08:13:50 +00:00
113 lines
5.1 KiB
TypeScript
113 lines
5.1 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { render, waitFor } from '@testing-library/react';
|
|
import type { InstalledPlugin } from '@/lib/plugin-types';
|
|
|
|
const evaluateShouldShow = vi.fn(async () => true);
|
|
let activePlugin: InstalledPlugin;
|
|
let hasShouldShow = false;
|
|
|
|
vi.mock('@/lib/plugin-sandbox/registry', () => ({
|
|
get: () => ({
|
|
plugin: activePlugin,
|
|
code: '',
|
|
tier: 'untrusted',
|
|
background: { evaluateShouldShow },
|
|
slotOffers: [{ name: 'email-banner', hasShouldShow, order: 100 }, { name: 'composer-sidebar-right', hasShouldShow: false, order: 100, fill: true }],
|
|
}),
|
|
}));
|
|
|
|
const createSlotInstance = vi.fn(() => ({ destroy: vi.fn(), updateProps: vi.fn(), setTheme: vi.fn() }));
|
|
vi.mock('@/lib/plugin-sandbox/host-bridge', () => ({
|
|
createSlotInstance: (...args: unknown[]) => createSlotInstance(...(args as [])),
|
|
}));
|
|
vi.mock('@/lib/plugin-sandbox/host-theme', () => ({ snapshotHostTheme: () => ({}) }));
|
|
|
|
import { PluginIframeSlot } from '../plugin-iframe-slot';
|
|
|
|
function plugin(perms: string[]): InstalledPlugin {
|
|
return {
|
|
id: 'p1', name: 'P', version: '1.0.0', author: '', description: '', type: 'ui-extension',
|
|
permissions: perms, grantedPermissions: perms, entrypoint: 'index.js',
|
|
enabled: true, status: 'running', settings: {},
|
|
};
|
|
}
|
|
|
|
const EMAIL = { id: 'm1', text: 'confidential body' };
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
hasShouldShow = false;
|
|
});
|
|
|
|
describe('PluginIframeSlot', () => {
|
|
it('lets a panel frame fill its panel instead of growing with its content', async () => {
|
|
activePlugin = plugin(['ui:composer-sidebar']);
|
|
const { container } = render(<PluginIframeSlot pluginId="p1" slot="composer-sidebar-right" extraProps={{}} fill />);
|
|
|
|
await waitFor(() => expect(createSlotInstance).toHaveBeenCalled());
|
|
const opts = (createSlotInstance.mock.calls[0] as unknown[])[0] as { fill?: boolean };
|
|
expect(opts.fill).toBe(true);
|
|
const wrapper = container.querySelector('[data-plugin-iframe-slot]') as HTMLElement;
|
|
expect(wrapper.dataset.pluginIframeFill).toBe('true');
|
|
expect(wrapper.style.flex).toBe('1 1 0%');
|
|
});
|
|
|
|
it('mounts a banner-only plugin without the opened message', async () => {
|
|
activePlugin = plugin(['ui:email-banner']);
|
|
render(<PluginIframeSlot pluginId="p1" slot="email-banner" extraProps={{ email: EMAIL }} />);
|
|
|
|
await waitFor(() => expect(createSlotInstance).toHaveBeenCalled());
|
|
const opts = (createSlotInstance.mock.calls[0] as unknown[])[0] as { extraProps: Record<string, unknown> };
|
|
expect(opts.extraProps.email).toBeUndefined();
|
|
});
|
|
|
|
it('does not show the message to shouldShow either', async () => {
|
|
activePlugin = plugin(['ui:email-banner']);
|
|
hasShouldShow = true;
|
|
render(<PluginIframeSlot pluginId="p1" slot="email-banner" extraProps={{ email: EMAIL }} />);
|
|
|
|
await waitFor(() => expect(evaluateShouldShow).toHaveBeenCalled());
|
|
expect((evaluateShouldShow.mock.calls[0] as unknown[])[1]).toEqual({});
|
|
});
|
|
|
|
it('hands the message to a plugin that may read mail', async () => {
|
|
activePlugin = plugin(['ui:email-banner', 'email:read']);
|
|
render(<PluginIframeSlot pluginId="p1" slot="email-banner" extraProps={{ email: EMAIL }} />);
|
|
|
|
await waitFor(() => expect(createSlotInstance).toHaveBeenCalled());
|
|
const opts = (createSlotInstance.mock.calls[0] as unknown[])[0] as { extraProps: Record<string, unknown> };
|
|
expect(opts.extraProps.email).toEqual(EMAIL);
|
|
});
|
|
});
|
|
|
|
describe('PluginIframeSlot re-renders', () => {
|
|
it('does not ask shouldShow again or push props when a re-render brings equal props', async () => {
|
|
activePlugin = plugin(['ui:email-banner', 'email:read']);
|
|
hasShouldShow = true;
|
|
// Callers that build props inline, like AppTopBannerSlot.
|
|
const { rerender } = render(<PluginIframeSlot pluginId="p1" slot="email-banner" extraProps={{ email: EMAIL }} />);
|
|
await waitFor(() => expect(createSlotInstance).toHaveBeenCalledTimes(1));
|
|
const instance = createSlotInstance.mock.results[0].value as { updateProps: ReturnType<typeof vi.fn> };
|
|
const pushes = instance.updateProps.mock.calls.length;
|
|
|
|
for (let i = 0; i < 5; i++) rerender(<PluginIframeSlot pluginId="p1" slot="email-banner" extraProps={{ email: EMAIL }} />);
|
|
await new Promise((r) => setTimeout(r, 0));
|
|
|
|
expect(evaluateShouldShow).toHaveBeenCalledTimes(1);
|
|
expect(instance.updateProps.mock.calls.length).toBe(pushes);
|
|
expect(createSlotInstance).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('still passes on props whose values changed', async () => {
|
|
activePlugin = plugin(['ui:email-banner', 'email:read']);
|
|
const { rerender } = render(<PluginIframeSlot pluginId="p1" slot="email-banner" extraProps={{ email: EMAIL }} />);
|
|
await waitFor(() => expect(createSlotInstance).toHaveBeenCalledTimes(1));
|
|
const instance = createSlotInstance.mock.results[0].value as { updateProps: ReturnType<typeof vi.fn> };
|
|
|
|
const next = { id: 'm2', text: 'other' };
|
|
rerender(<PluginIframeSlot pluginId="p1" slot="email-banner" extraProps={{ email: next }} />);
|
|
|
|
await waitFor(() => expect(instance.updateProps).toHaveBeenLastCalledWith({ email: next }));
|
|
});
|
|
});
|