mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-04 08:15:41 +00:00
fix: keep empty plugin frames rendered so they can report a height, and out of the Tab order
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, waitFor } from '@testing-library/react';
|
||||
import { act, render, waitFor } from '@testing-library/react';
|
||||
import type { InstalledPlugin } from '@/lib/plugin-types';
|
||||
|
||||
const evaluateShouldShow = vi.fn(async () => true);
|
||||
@@ -12,7 +12,11 @@ vi.mock('@/lib/plugin-sandbox/registry', () => ({
|
||||
code: '',
|
||||
tier: 'untrusted',
|
||||
background: { evaluateShouldShow },
|
||||
slotOffers: [{ name: 'email-banner', hasShouldShow, order: 100 }, { name: 'composer-sidebar-right', hasShouldShow: false, order: 100, fill: true }],
|
||||
slotOffers: [
|
||||
{ name: 'email-banner', hasShouldShow, order: 100 },
|
||||
{ name: 'composer-sidebar-right', hasShouldShow: false, order: 100, fill: true },
|
||||
{ name: 'toolbar-actions', hasShouldShow: false, order: 100 },
|
||||
],
|
||||
}),
|
||||
}));
|
||||
|
||||
@@ -87,6 +91,43 @@ describe('PluginIframeSlot', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('PluginIframeSlot with nothing to show yet', () => {
|
||||
type SlotOptions = { onResize: (height: number, width?: number) => void };
|
||||
const reportSize = (height: number, width?: number) => act(() => {
|
||||
((createSlotInstance.mock.calls[0] as unknown[])[0] as SlotOptions).onResize(height, width);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['block', 'email-banner', ['ui:email-banner']],
|
||||
['inline', 'toolbar-actions', ['ui:toolbar']],
|
||||
] as const)('keeps a %s frame rendered and out of the Tab order until it reports a height', async (_kind, slot, perms) => {
|
||||
activePlugin = plugin([...perms]);
|
||||
const { container } = render(<PluginIframeSlot pluginId="p1" slot={slot} extraProps={{}} />);
|
||||
await waitFor(() => expect(createSlotInstance).toHaveBeenCalled());
|
||||
const wrapper = container.querySelector('[data-plugin-iframe-slot]') as HTMLElement;
|
||||
|
||||
expect(wrapper).toHaveAttribute('inert');
|
||||
// Hidden, an isolated frame stops rendering and can never report the height that would unhide it.
|
||||
expect(wrapper.style.visibility).toBe('');
|
||||
|
||||
reportSize(32, 100);
|
||||
expect(wrapper).not.toHaveAttribute('inert');
|
||||
expect(wrapper.style.height).toBe('32px');
|
||||
|
||||
// Its content went away: nothing to land on again, still rendered.
|
||||
reportSize(0, 100);
|
||||
expect(wrapper).toHaveAttribute('inert');
|
||||
expect(wrapper.style.visibility).toBe('');
|
||||
});
|
||||
|
||||
it('leaves a frame that fills its panel alone: its height never comes from the frame', async () => {
|
||||
activePlugin = plugin(['ui:composer-sidebar']);
|
||||
const { container } = render(<PluginIframeSlot pluginId="p1" slot="composer-sidebar-right" extraProps={{}} fill />);
|
||||
await waitFor(() => expect(createSlotInstance).toHaveBeenCalled());
|
||||
expect(container.querySelector('[data-plugin-iframe-slot]')).not.toHaveAttribute('inert');
|
||||
});
|
||||
});
|
||||
|
||||
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']);
|
||||
|
||||
@@ -190,9 +190,12 @@ export function PluginIframeSlot({ pluginId, slot, extraProps, fill = false, min
|
||||
<div
|
||||
ref={wrapperRef}
|
||||
style={inline
|
||||
? { display: 'inline-block', verticalAlign: 'middle', height, width: width ?? 0, maxWidth: '100%', overflow: 'hidden', visibility: height > 0 ? undefined : 'hidden' }
|
||||
// A frame with nothing to show must not be a stop for Tab, where the focus would vanish.
|
||||
: fill ? { flex: '1 1 0%', minHeight: minFillHeight ?? 0 } : { height, minHeight: height, visibility: height > 0 ? undefined : 'hidden' }}
|
||||
? { display: 'inline-block', verticalAlign: 'middle', height, width: width ?? 0, maxWidth: '100%', overflow: 'hidden' }
|
||||
: fill ? { flex: '1 1 0%', minHeight: minFillHeight ?? 0 } : { height, minHeight: height }}
|
||||
// A frame with nothing to show must not be a stop for Tab, where the
|
||||
// focus would vanish. Not visibility: hidden, because the browser stops
|
||||
// rendering an isolated frame then, and only its rendering reports a height.
|
||||
inert={!fill && height === 0}
|
||||
data-plugin-iframe-slot={`${pluginId}:${slot}`}
|
||||
data-plugin-iframe-fill={fill || undefined}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user