Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions bulma-ui/src/form/DateInputBase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -332,14 +332,17 @@ export const DateInputBase = forwardRef<HTMLInputElement, DateInputBaseProps>(
const containerClass = usePrefixedClassNames('dateinput-container');
const triggerClass = usePrefixedClassNames('dateinput-trigger');

// What the latest request asked for, so a second request in the same
// event sees it before React renders. The callbacks fire here rather than
// in a state updater, which StrictMode calls twice.
const requestedOpenRef = useRef(false);
const setOpen = useCallback(
(next: boolean) => {
setOpenState(prev => {
if (prev === next) return prev;
if (next) onOpen?.();
else onClose?.();
return next;
});
if (requestedOpenRef.current === next) return;
requestedOpenRef.current = next;
if (next) onOpen?.();
else onClose?.();
Comment thread
claude[bot] marked this conversation as resolved.
Resolved
setOpenState(next);
},
[onOpen, onClose]
);
Expand Down
23 changes: 13 additions & 10 deletions bulma-ui/src/form/DateTimeInputBase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -363,18 +363,21 @@ export const DateTimeInputBase = forwardRef<
'datetimeinput-footer-time-pill'
);

// What the latest request asked for, so a second request in the same event
// sees it before React renders. The callbacks fire here rather than in a
// state updater, which StrictMode calls twice.
const requestedOpenRef = useRef(false);
const setOpen = useCallback(
(next: boolean) => {
setOpenState(prev => {
if (prev === next) return prev;
if (next) {
valueAtOpenRef.current = value;
onOpen?.();
} else {
onClose?.();
}
return next;
});
if (requestedOpenRef.current === next) return;
requestedOpenRef.current = next;
if (next) {
valueAtOpenRef.current = value;
onOpen?.();
} else {
onClose?.();
}
setOpenState(next);
},
[onOpen, onClose, value]
);
Expand Down
23 changes: 13 additions & 10 deletions bulma-ui/src/form/TimeInputBase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -304,18 +304,21 @@ export const TimeInputBase = forwardRef<HTMLInputElement, TimeInputBaseProps>(
'timeinput-footer-done'
);

// What the latest request asked for, so a second request in the same
// event sees it before React renders. The callbacks fire here rather than
// in a state updater, which StrictMode calls twice.
const requestedOpenRef = useRef(false);
const setOpen = useCallback(
(next: boolean) => {
setOpenState(prev => {
if (prev === next) return prev;
if (next) {
valueAtOpenRef.current = value;
onOpen?.();
} else {
onClose?.();
}
return next;
});
if (requestedOpenRef.current === next) return;
requestedOpenRef.current = next;
if (next) {
valueAtOpenRef.current = value;
onOpen?.();
} else {
onClose?.();
}
setOpenState(next);
},
[onOpen, onClose, value]
);
Expand Down
44 changes: 44 additions & 0 deletions bulma-ui/src/form/__tests__/DateInput.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1989,3 +1989,47 @@ describe('DateInput focus handed back on close', () => {
});
});
});

describe('DateInput onOpen and onClose', () => {
const june15 = new Date(2024, 5, 15);
const pressEscape = (on: Element) =>
act(() => {
fireEvent.keyDown(on, { key: 'Escape' });
});

it('fire once per open and close under StrictMode', () => {
const onOpen = jest.fn();
const onClose = jest.fn();
const { getByRole, queryByRole } = render(
<React.StrictMode>
<DateInput defaultValue={june15} onOpen={onOpen} onClose={onClose} />
</React.StrictMode>
);
act(() => {
getByRole('combobox').focus();
});
expect(getByRole('dialog')).toBeInTheDocument();
pressEscape(document.activeElement!);
expect(queryByRole('dialog')).toBeNull();
expect(onOpen).toHaveBeenCalledTimes(1);
expect(onClose).toHaveBeenCalledTimes(1);
});

it('fires onClose once when the input and the popover both take Escape', () => {
Comment thread
claude[bot] marked this conversation as resolved.
Resolved
const onClose = jest.fn();
const { getByRole, queryByRole } = render(
<DateInput defaultValue={june15} onClose={onClose} openOnFocus={false} />
);
const input = getByRole('combobox');
act(() => {
fireEvent.click(getByRole('button', { name: 'Choose date' }));
});
act(() => {
input.focus();
});
expect(getByRole('dialog')).toBeInTheDocument();
pressEscape(input);
expect(queryByRole('dialog')).toBeNull();
expect(onClose).toHaveBeenCalledTimes(1);
});
});
44 changes: 44 additions & 0 deletions bulma-ui/src/form/__tests__/DateTimeInput.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1397,3 +1397,47 @@ describe('DateTimeInput focus handed back on close', () => {
});
});
});

describe('DateTimeInput onOpen and onClose', () => {
const v = new Date(2024, 5, 15, 9, 30);
const pressEscape = (on: Element) =>
act(() => {
fireEvent.keyDown(on, { key: 'Escape' });
});

it('fire once per open and close under StrictMode', () => {
const onOpen = jest.fn();
const onClose = jest.fn();
const { getByRole, queryByRole } = render(
<React.StrictMode>
<DateTimeInput defaultValue={v} onOpen={onOpen} onClose={onClose} />
</React.StrictMode>
);
act(() => {
getByRole('combobox').focus();
});
expect(getByRole('dialog')).toBeInTheDocument();
pressEscape(document.activeElement!);
expect(queryByRole('dialog')).toBeNull();
expect(onOpen).toHaveBeenCalledTimes(1);
expect(onClose).toHaveBeenCalledTimes(1);
});

it('fires onClose once when the input and the popover both take Escape', () => {
const onClose = jest.fn();
const { getByRole, queryByRole } = render(
<DateTimeInput defaultValue={v} onClose={onClose} openOnFocus={false} />
);
const input = getByRole('combobox');
act(() => {
fireEvent.click(getByRole('button', { name: 'Choose date and time' }));
});
act(() => {
input.focus();
});
expect(getByRole('dialog')).toBeInTheDocument();
pressEscape(input);
expect(queryByRole('dialog')).toBeNull();
expect(onClose).toHaveBeenCalledTimes(1);
});
});
47 changes: 47 additions & 0 deletions bulma-ui/src/form/__tests__/TimeInput.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1715,3 +1715,50 @@ describe('TimeInput focus handed back on close', () => {
});
});
});

describe('TimeInput onOpen and onClose', () => {
const pressEscape = (on: Element) =>
act(() => {
fireEvent.keyDown(on, { key: 'Escape' });
});

it('fire once per open and close under StrictMode', () => {
const onOpen = jest.fn();
const onClose = jest.fn();
const { getByRole, queryByRole } = render(
<React.StrictMode>
<TimeInput defaultValue={at(9, 30)} onOpen={onOpen} onClose={onClose} />
</React.StrictMode>
);
act(() => {
getByRole('combobox').focus();
});
expect(getByRole('dialog')).toBeInTheDocument();
pressEscape(document.activeElement!);
expect(queryByRole('dialog')).toBeNull();
expect(onOpen).toHaveBeenCalledTimes(1);
expect(onClose).toHaveBeenCalledTimes(1);
});

it('fires onClose once when the input and the popover both take Escape', () => {
const onClose = jest.fn();
const { getByRole, queryByRole } = render(
<TimeInput
defaultValue={at(9, 30)}
onClose={onClose}
openOnFocus={false}
/>
);
const input = getByRole('combobox');
act(() => {
fireEvent.click(getByRole('button', { name: 'Choose time' }));
});
act(() => {
input.focus();
});
expect(getByRole('dialog')).toBeInTheDocument();
pressEscape(input);
expect(queryByRole('dialog')).toBeNull();
expect(onClose).toHaveBeenCalledTimes(1);
});
});
Loading