eventComposerPopoverDismiss.test.tsx4.5 KBView on GitHub import { act, createEvent, fireEvent, render, screen } from '@testing-library/react';
import React from 'react';
/**
* How the composer popover can, and cannot, be dismissed.
*
* Both assertions here guard bugs that are invisible in the source.
*
* Escape: this repo's `PopoverContent` calls `stopPropagation()` on Escape, and Radix listens
* in the CAPTURE phase — so a `window` keydown listener inside the form never runs while the
* popover is open. The draft therefore has to be dropped by the popover's own close. Wire it
* the "obvious" way instead and Escape silently does nothing.
*
* Outside clicks, once something is typed: the grid underneath stays live so the draft block
* can be dragged to another time while the form is up. If an outside click closed the composer,
* moving the event would throw away everything typed into it.
*
* Outside clicks while it is still EMPTY: the opposite. A transparent backdrop makes the
* composer modal, so the click means "not this" and nothing else — it never reaches the grid to
* re-time a blank block the user has already given up on.
*/
jest.mock('@/modules/calendar/components/EventComposerForm', () => ({
EventComposerForm: () => {
const R = require('react');
return R.createElement('div', { 'data-testid': 'composer-form' }, 'COMPOSER');
},
}));
import { EventComposerPopover } from '@/modules/calendar/components/EventComposerPopover';
import { useCedarStore } from '@/modules/cedar-os/src/store/CedarStore';
function setDraft({ hasContent }: { hasContent: boolean }) {
act(() => {
useCedarStore.setState((s) => ({
...s,
draftCalendarEvent: {
id: 'new-1',
summary: hasContent ? 'Intro call' : '',
start: { dateTime: '2026-08-28T17:00:00.000Z' },
end: { dateTime: '2026-08-28T17:30:00.000Z' },
},
// What the composer publishes as its fields are filled in — the popover reads it to
// decide whether the calendar behind it is live or behind glass.
composerHasContent: hasContent,
}));
});
}
function renderComposer() {
return render(
<EventComposerPopover>
<div data-testid="draft-block">DRAFT</div>
</EventComposerPopover>,
);
}
describe('EventComposerPopover dismissal', () => {
it('opens pinned to the draft block', () => {
setDraft({ hasContent: true });
renderComposer();
expect(screen.getByTestId('draft-block')).toBeInTheDocument();
expect(screen.getByTestId('composer-form')).toBeInTheDocument();
});
it('drops the draft on Escape', () => {
setDraft({ hasContent: true });
renderComposer();
expect(useCedarStore.getState().draftCalendarEvent).not.toBeNull();
act(() => {
fireEvent.keyDown(document, { key=[redacted] });
});
expect(useCedarStore.getState().draftCalendarEvent).toBeNull();
});
it('keeps a started draft when the grid underneath is clicked', () => {
setDraft({ hasContent: true });
const { container } = renderComposer();
// Stands in for the grid: dragging the block to a new time is an interaction OUTSIDE the
// popover, and must not be read as "cancel".
const outside = document.createElement('div');
document.body.appendChild(outside);
act(() => {
fireEvent.pointerDown(outside);
fireEvent.mouseDown(outside);
fireEvent.click(outside);
});
expect(useCedarStore.getState().draftCalendarEvent).not.toBeNull();
expect(container).toBeTruthy();
document.body.removeChild(outside);
});
it('leaves the calendar live once something has been typed', () => {
setDraft({ hasContent: true });
renderComposer();
expect(screen.queryByTestId('composer-backdrop')).not.toBeInTheDocument();
});
it('covers the calendar while nothing has been typed', () => {
setDraft({ hasContent: false });
renderComposer();
expect(screen.getByTestId('composer-backdrop')).toBeInTheDocument();
});
it('drops an untouched draft on the first click outside, and swallows that click', () => {
setDraft({ hasContent: false });
renderComposer();
const backdropClick = createEvent.pointerDown(screen.getByTestId('composer-backdrop'), {
bubbles: true,
cancelable: true,
});
act(() => {
fireEvent(screen.getByTestId('composer-backdrop'), backdropClick);
});
expect(useCedarStore.getState().draftCalendarEvent).toBeNull();
// Nullified, not passed on: the grid starts drag-to-create on `pointerdown`, so anything
// that survives this event becomes a second draft where the user clicked.
expect(backdropClick.defaultPrevented).toBe(true);
});
});