{
expect(screen.getByText('Confirm')).toBeInTheDocument();
expect(screen.getByText('Cancel')).toBeInTheDocument();
});
+
+ test('calls onExited when modal exits', async () => {
+ const onExitedMock = jest.fn();
+ const props = {
+ ...baseProps,
+ onExited: onExitedMock,
+ };
+ render(
+ wrapIntl(
),
+ );
+
+ // Find and click the close button to trigger modal exit
+ const closeButton = screen.getByLabelText('Close');
+ closeButton.click();
+
+ // Wait for onExited to be called
+ await waitFor(() => {
+ expect(onExitedMock).toHaveBeenCalled();
+ });
+ });
+
+ test('does not throw if onExited is undefined', async () => {
+ // Create props without onExited
+ const {onExited, ...propsWithoutOnExited} = baseProps; // eslint-disable-line @typescript-eslint/no-unused-vars
+ const props = {
+ ...propsWithoutOnExited,
+ onHide: jest.fn(), // Ensure onHide is provided since it's mandatory
+ };
+
+ // This should not throw
+ render(
+ wrapIntl(
),
+ );
+
+ // Find and click the close button to trigger modal exit
+ const closeButton = screen.getByLabelText('Close');
+
+ // This should not throw
+ expect(() => {
+ closeButton.click();
+ }).not.toThrow();
+ });
+
+ test('calls onEntered when modal enters', async () => {
+ const onEnteredMock = jest.fn();
+ const props = {
+ ...baseProps,
+ onEntered: onEnteredMock,
+ show: false, // Start with modal hidden
+ };
+
+ const {rerender} = render(
+ wrapIntl(
),
+ );
+
+ // Show the modal
+ rerender(
+ wrapIntl(
),
+ );
+
+ // Wait for onEntered to be called
+ await waitFor(() => {
+ expect(onEnteredMock).toHaveBeenCalled();
+ });
+ });
+
+ test('does not throw if onEntered is undefined', async () => {
+ // Create props without onEntered
+ const props = {
+ ...baseProps,
+ show: false, // Start with modal hidden
+ };
+
+ const {rerender} = render(
+ wrapIntl(),
+ );
+
+ // This should not throw
+ expect(() => {
+ rerender(
+ wrapIntl(),
+ );
+ }).not.toThrow();
+ });
+
+ test('calls onHide when modal is closed', async () => {
+ const onHideMock = jest.fn();
+ const props = {
+ ...baseProps,
+ onHide: onHideMock,
+ };
+ render(
+ wrapIntl(),
+ );
+
+ // Find and click the close button to trigger modal exit
+ const closeButton = screen.getByLabelText('Close');
+ closeButton.click();
+
+ // Wait for onHide to be called
+ await waitFor(() => {
+ expect(onHideMock).toHaveBeenCalled();
+ });
+ });
});
diff --git a/webapp/platform/components/src/generic_modal/generic_modal.tsx b/webapp/platform/components/src/generic_modal/generic_modal.tsx
index 9ce4b4a826..b7d2b6bbd9 100644
--- a/webapp/platform/components/src/generic_modal/generic_modal.tsx
+++ b/webapp/platform/components/src/generic_modal/generic_modal.tsx
@@ -8,9 +8,11 @@ import {FormattedMessage} from 'react-intl';
import './generic_modal.scss';
+export type ModalLocation = 'top' | 'center' | 'bottom';
+
export type Props = {
className?: string;
- onExited: () => void;
+ onExited?: () => void;
onEntered?: () => void;
onHide?: () => void;
modalHeaderText?: React.ReactNode;
@@ -32,6 +34,7 @@ export type Props = {
enforceFocus?: boolean;
container?: React.ReactNode | React.ReactNodeArray;
ariaLabel?: string;
+ ariaLabelledby?: string;
errorText?: string | React.ReactNode;
compassDesign?: boolean;
backdrop?: boolean | 'static';
@@ -48,6 +51,22 @@ export type Props = {
footerDivider?: boolean;
appendedContent?: React.ReactNode;
headerButton?: React.ReactNode;
+ showCloseButton?: boolean;
+ showHeader?: boolean;
+
+ /*
+ * Controls the vertical location of the modal.
+ * 'top' => margin-top: 5vh
+ * 'center' => margin-top: calc(50vh - 240px)
+ * 'bottom' => margin-top: calc(50vh + 240px) (example calculation)
+ */
+ modalLocation?: ModalLocation;
+
+ /**
+ * Optionally set a test ID for the container, so that the modal can be easily referenced
+ * in tests (Cypress, Playwright, etc.)
+ */
+ dataTestId?: string;
};
type State = {
@@ -63,6 +82,9 @@ export class GenericModal extends React.PureComponent {
enforceFocus: true,
keyboardEscape: true,
bodyPadding: true,
+ showCloseButton: true,
+ showHeader: true,
+ modalLocation: 'center',
};
constructor(props: Props) {
@@ -184,13 +206,33 @@ export class GenericModal extends React.PureComponent {