test: check the calendar picker and the kit checkbox against the app's one menu, which draws a tick instead of a box and closes on page scroll after it opens

The booking calendar picker is now the app's context menu: its header is the menu's header row, it lays out from its measured width and starts listening for scroll a tick after it opens. Menu check items show a tick instead of a drawn box, so the kit checkbox copies the Files select box only.
This commit is contained in:
Linus Rath
2026-11-04 20:49:57 +01:00
parent 7dcc1d22b5
commit a948e7fc26
3 changed files with 23 additions and 5 deletions
@@ -349,7 +349,12 @@ describe('BookingCalendarActions', () => {
expect(document.activeElement).toBe(items[2]);
fireEvent.keyDown(menu, { key: 'Home' });
expect(document.activeElement).toBe(items[0]);
expect(menu.querySelector('div')?.getAttribute('role')).toBe('presentation');
// The heading is the app menu's header row: not an item, so the keys skip it.
const header = menu.querySelector<HTMLElement>('[data-context-menu-header]');
expect(header?.textContent).toBe('select_calendar');
expect(header?.closest('[role="menuitem"]')).toBeNull();
expect(header?.hasAttribute('tabindex')).toBe(false);
expect(items.map((item) => item.textContent)).toEqual(['Personal', 'Work', 'Travel']);
});
it('closes the picker on Escape and gives focus back to its button', async () => {
@@ -361,9 +366,12 @@ describe('BookingCalendarActions', () => {
it('stays open while its own list scrolls, and closes when the page scrolls', async () => {
const { menu } = await openPicker();
fireEvent.scroll(menu);
// The menu starts listening a tick after it opens, so the click that opened it doesn't close it.
await act(async () => { await new Promise((resolve) => setTimeout(resolve, 0)); });
fireEvent.scroll(menu.firstElementChild ?? menu);
expect(screen.queryByRole('menu')).not.toBeNull();
fireEvent.scroll(window);
// A page scroll is fired at the document.
fireEvent.scroll(document);
expect(screen.queryByRole('menu')).toBeNull();
});
@@ -378,6 +386,13 @@ describe('BookingCalendarActions', () => {
useCalendarStore.setState({ calendars: [cal('Personal', { isDefault: true }), cal('Work')] });
const width = window.innerWidth;
Object.defineProperty(window, 'innerWidth', { configurable: true, value: 390 });
// jsdom lays nothing out: the menu measures as 208 px wide, as it is on a phone.
const measure = vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockImplementation(function (this: HTMLElement) {
const isMenu = this.getAttribute('role') === 'menu';
const w = isMenu ? 208 : 0;
const h = isMenu ? 120 : 0;
return { left: 0, right: w, top: 0, bottom: h, width: w, height: h, x: 0, y: 0, toJSON: () => ({}) };
});
try {
render(<BookingCalendarActions booking={await bookingFrom([OUT])} email={EMAIL} />);
const button = await screen.findByRole('button', { name: /add_to_calendar/ });
@@ -388,6 +403,7 @@ describe('BookingCalendarActions', () => {
expect(left).toBeGreaterThanOrEqual(8);
expect(left + 208).toBeLessThanOrEqual(390 - 8);
} finally {
measure.mockRestore();
Object.defineProperty(window, 'innerWidth', { configurable: true, value: width });
}
});
+3 -1
View File
@@ -65,7 +65,9 @@ describe('plugin UI kit components', () => {
['components/settings/settings-section.tsx', 'bg-muted-foreground ltr:translate-x-1 rtl:-translate-x-1'],
['components/settings/settings-section.tsx', 'flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between sm:gap-4 py-3 border-b border-border last:border-0'],
['components/settings/settings-section.tsx', 'px-3 py-1.5 text-xs rounded-md transition-colors duration-150'],
['components/email/composer-toolbar-menu.tsx', 'mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-sm border'],
['components/files/file-views.tsx', 'w-4 h-4 rounded border flex items-center justify-center shrink-0'],
['components/files/file-views.tsx', 'bg-primary border-primary text-primary-foreground'],
['components/files/file-views.tsx', 'border-muted-foreground/50 bg-background'],
['components/calendar/ical-import-modal.tsx', 'flex items-center gap-1.5 px-4 py-2 text-sm font-medium border-b-2 transition-colors'],
['components/ui/empty-state.tsx', 'w-14 h-14 rounded-full bg-muted flex items-center justify-center mb-4'],
['components/files/file-views.tsx', 'border-b border-border/60'],
+1 -1
View File
@@ -669,7 +669,7 @@ export interface KitCheckboxProps {
id?: string;
}
/** The composer's check items and the Files app's select box: a drawn box, not the browser's. */
/** The Files app's select box: a drawn box, not the browser's. */
export function Checkbox({ checked, onChange, label, hint, disabled = false, id, ariaLabel }: KitCheckboxProps) {
const autoId = React.useId();
const boxId = id ?? autoId;