Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
import type { FC } from 'react';
import { useState, useCallback } from 'react';
import type { WarningModalProps } from '@patternfly/react-component-groups';
import { WarningModal } from '@patternfly/react-component-groups';
import type { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider';
import { useOverlay } from '@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay';

/**
* ControlledWarningModal is a wrapper around WarningModal that manages its open state.
*/
const ControlledWarningModal: FC<WarningModalProps> = (props) => {
const ControlledWarningModal: OverlayComponent<WarningModalProps> = ({
closeOverlay,
...props
}) => {
const [isOpen, setIsOpen] = useState(true);

const onClose: WarningModalProps['onClose'] = (e) => {
setIsOpen(false);
props.onClose?.(e);
closeOverlay?.();
};

const onConfirm: WarningModalProps['onConfirm'] = () => {
setIsOpen(false);
props.onConfirm?.();
closeOverlay?.();
};

return <WarningModal {...props} isOpen={isOpen} onClose={onClose} onConfirm={onConfirm} />;
Expand Down
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you check if these mocks are used anywhere? I can't find any references

Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
/**
* Mock implementation of error-modal-handler for Jest tests
*
* Note: This mock provides a simplified SyncModalLaunchers that doesn't call
* useSyncWarningModalLauncher. Tests that need warning modal functionality
* should explicitly mock warning-modal-handler or use the real implementation.
*/

export const mockLaunchErrorModal = jest.fn();

export const SyncErrorModalLauncher = () => null;
export const useSyncErrorModalLauncher = jest.fn();

export const useErrorModalLauncher = jest.fn(() => mockLaunchErrorModal);
// Simplified component that doesn't sync warning modals
// Tests needing both error and warning modals should not use this mock
export const SyncModalLaunchers = () => null;

export const launchErrorModal = mockLaunchErrorModal;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you check if these mocks are used anywhere? I can't find any references in a quick search

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Mock for warning-modal-handler
* Used in Jest tests to avoid rendering actual modals
*/

export const mockLaunchWarningModal = jest.fn((props, onConfirm) => {
// Immediately call onConfirm by default to simulate user confirming
onConfirm?.();
});

export const useSyncWarningModalLauncher = jest.fn();

export const launchWarningModal = mockLaunchWarningModal;
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { render } from '@testing-library/react';
import {
SyncErrorModalLauncher,
useErrorModalLauncher,
launchErrorModal,
} from '../error-modal-handler';
import { SyncModalLaunchers, launchErrorModal } from '../error-modal-handler';

// Mock useOverlay
const mockLauncher = jest.fn();
Expand All @@ -16,9 +12,9 @@ describe('error-modal-handler', () => {
jest.clearAllMocks();
});

describe('SyncErrorModalLauncher', () => {
describe('SyncModalLaunchers', () => {
it('should sync the launcher on mount', () => {
render(<SyncErrorModalLauncher />);
render(<SyncModalLaunchers />);

// Call the module-level function
launchErrorModal({ error: 'Test error', title: 'Test' });
Expand All @@ -31,46 +27,20 @@ describe('error-modal-handler', () => {
});

it('should cleanup launcher on unmount', () => {
const { unmount } = render(<SyncErrorModalLauncher />);
const { unmount } = render(<SyncModalLaunchers />);

unmount();

// Should log error instead of crashing
const consoleError = jest.spyOn(console, 'error').mockImplementation();
launchErrorModal({ error: 'Test error' });

expect(consoleError).toHaveBeenCalledWith(
expect.stringContaining('Error modal launcher not initialized'),
expect.any(Object),
);

consoleError.mockRestore();
});
});

describe('useErrorModalLauncher', () => {
it('should return a function that launches error modals', () => {
let capturedLauncher: any;

const TestComponent = () => {
capturedLauncher = useErrorModalLauncher();
return null;
};

render(<TestComponent />);

capturedLauncher({ error: 'Test error', title: 'Test Title' });

expect(mockLauncher).toHaveBeenCalledWith(expect.anything(), {
error: 'Test error',
title: 'Test Title',
});
// Should throw error when launcher is not initialized
expect(() => {
launchErrorModal({ error: 'Test error' });
}).toThrow('Error modal launcher not initialized');
});
});

describe('launchErrorModal', () => {
it('should launch error modal when launcher is initialized', () => {
render(<SyncErrorModalLauncher />);
render(<SyncModalLaunchers />);

launchErrorModal({
error: 'Connection failed',
Expand All @@ -83,17 +53,10 @@ describe('error-modal-handler', () => {
});
});

it('should log error when launcher is not initialized', () => {
const consoleError = jest.spyOn(console, 'error').mockImplementation();

launchErrorModal({ error: 'Test error' });

expect(consoleError).toHaveBeenCalledWith(
expect.stringContaining('Error modal launcher not initialized'),
{ error: 'Test error' },
);

consoleError.mockRestore();
it('should throw error when launcher is not initialized', () => {
expect(() => {
launchErrorModal({ error: 'Test error' });
}).toThrow('Error modal launcher not initialized');
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import { render } from '@testing-library/react';
import { useSyncWarningModalLauncher, launchWarningModal } from '../warning-modal-handler';

// Mock useOverlay
const mockLauncher = jest.fn();
jest.mock('@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay', () => ({
useOverlay: () => mockLauncher,
}));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thinking if it's better to add OverlayProvider to renderWithProviders, and then use that. that way we are testing more code


describe('warning-modal-handler', () => {
beforeEach(() => {
jest.clearAllMocks();
});

describe('useSyncWarningModalLauncher', () => {
it('should sync the launcher on mount', () => {
const TestComponent = () => {
useSyncWarningModalLauncher();
return null;
};

render(<TestComponent />);

const onConfirm = jest.fn();
const onCancel = jest.fn();

// Call the module-level function
launchWarningModal(
{
title: 'Test Warning',
children: 'Are you sure?',
confirmButtonLabel: 'Confirm',
},
onConfirm,
onCancel,
);

// Should have called the mocked overlay launcher
expect(mockLauncher).toHaveBeenCalledWith(expect.anything(), {
title: 'Test Warning',
children: 'Are you sure?',
confirmButtonLabel: 'Confirm',
onConfirm: expect.any(Function),
onClose: expect.any(Function),
});
});

it('should cleanup launcher on unmount', () => {
const TestComponent = () => {
useSyncWarningModalLauncher();
return null;
};

const { unmount } = render(<TestComponent />);

unmount();

// Should throw error when launcher is not initialized
expect(() => {
launchWarningModal(
{
title: 'Test Warning',
children: 'Are you sure?',
},
jest.fn(),
);
}).toThrow('Warning modal launcher not initialized');
});
});

describe('launchWarningModal', () => {
it('should launch warning modal when launcher is initialized', () => {
const TestComponent = () => {
useSyncWarningModalLauncher();
return null;
};

render(<TestComponent />);

const onConfirm = jest.fn();
const onCancel = jest.fn();

launchWarningModal(
{
title: 'Delete Resource',
children: 'Are you sure you want to delete?',
confirmButtonLabel: 'Delete',
},
onConfirm,
onCancel,
);

expect(mockLauncher).toHaveBeenCalledWith(expect.anything(), {
title: 'Delete Resource',
children: 'Are you sure you want to delete?',
confirmButtonLabel: 'Delete',
onConfirm: expect.any(Function),
onClose: expect.any(Function),
});
});

it('should throw error when launcher is not initialized', () => {
expect(() => {
launchWarningModal(
{
title: 'Test',
children: 'Test message',
},
jest.fn(),
);
}).toThrow('Warning modal launcher not initialized');
});

it('should call onConfirm callback when user confirms', () => {
const TestComponent = () => {
useSyncWarningModalLauncher();
return null;
};

render(<TestComponent />);

const onConfirm = jest.fn();
const onCancel = jest.fn();

launchWarningModal(
{
title: 'Confirm Action',
children: 'Proceed?',
},
onConfirm,
onCancel,
);

// Get the onConfirm callback that was passed to the launcher
const launcherCall = mockLauncher.mock.calls[0];
const launcherProps = launcherCall[1];
launcherProps.onConfirm();

expect(onConfirm).toHaveBeenCalled();
expect(onCancel).not.toHaveBeenCalled();
});

it('should call onCancel callback when user cancels', () => {
const TestComponent = () => {
useSyncWarningModalLauncher();
return null;
};

render(<TestComponent />);

const onConfirm = jest.fn();
const onCancel = jest.fn();

launchWarningModal(
{
title: 'Confirm Action',
children: 'Proceed?',
},
onConfirm,
onCancel,
);

// Get the onClose callback that was passed to the launcher
const launcherCall = mockLauncher.mock.calls[0];
const launcherProps = launcherCall[1];
launcherProps.onClose();

expect(onCancel).toHaveBeenCalled();
expect(onConfirm).not.toHaveBeenCalled();
});

it('should handle optional callbacks gracefully', () => {
const TestComponent = () => {
useSyncWarningModalLauncher();
return null;
};

render(<TestComponent />);

// Call without callbacks
expect(() => {
launchWarningModal({
title: 'Info',
children: 'Just showing info',
});
}).not.toThrow();

expect(mockLauncher).toHaveBeenCalled();

// Verify calling the callbacks doesn't throw
const launcherCall = mockLauncher.mock.calls[0];
const launcherProps = launcherCall[1];

expect(() => {
launcherProps.onConfirm();
launcherProps.onClose();
}).not.toThrow();
});
});
});
Loading