diff --git a/webpack/ForemanTasks/Components/TaskActions/UnlockModals.js b/webpack/ForemanTasks/Components/TaskActions/UnlockModals.js deleted file mode 100644 index 7d02c37d4..000000000 --- a/webpack/ForemanTasks/Components/TaskActions/UnlockModals.js +++ /dev/null @@ -1,60 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { translate as __, sprintf } from 'foremanReact/common/I18n'; -import { ClickConfirmation } from '../common/ClickConfirmation'; -import { UNLOCK_MODAL, FORCE_UNLOCK_MODAL } from './TaskActionsConstants'; - -const confirmationMessage = __( - 'I understand that this may cause harm and have working database backups of all backend services.' -); - -export const UnlockModal = ({ onClick, id }) => ( - -); - -export const ForceUnlockModal = ({ onClick, id, selectedRowsLen }) => ( - -); - -UnlockModal.propTypes = { - onClick: PropTypes.func.isRequired, - id: PropTypes.string, -}; - -ForceUnlockModal.propTypes = { - onClick: PropTypes.func.isRequired, - id: PropTypes.string, - selectedRowsLen: PropTypes.number, -}; - -UnlockModal.defaultProps = { - id: UNLOCK_MODAL, -}; - -ForceUnlockModal.defaultProps = { - id: FORCE_UNLOCK_MODAL, - selectedRowsLen: 1, -}; diff --git a/webpack/ForemanTasks/Components/TaskActions/UnlockModals.test.js b/webpack/ForemanTasks/Components/TaskActions/UnlockModals.test.js deleted file mode 100644 index 96802bbbe..000000000 --- a/webpack/ForemanTasks/Components/TaskActions/UnlockModals.test.js +++ /dev/null @@ -1,14 +0,0 @@ -import { testComponentSnapshotsWithFixtures } from '@theforeman/test'; - -import { UnlockModal, ForceUnlockModal } from './UnlockModals'; - -const fixtures = { - render: { taskID: 'some-id', onClick: jest.fn() }, -}; - -describe('UnlockModal', () => { - testComponentSnapshotsWithFixtures(UnlockModal, fixtures); -}); -describe('ForceUnlockModal', () => { - testComponentSnapshotsWithFixtures(ForceUnlockModal, fixtures); -}); diff --git a/webpack/ForemanTasks/Components/TaskActions/__snapshots__/UnlockModals.test.js.snap b/webpack/ForemanTasks/Components/TaskActions/__snapshots__/UnlockModals.test.js.snap deleted file mode 100644 index c5786395a..000000000 --- a/webpack/ForemanTasks/Components/TaskActions/__snapshots__/UnlockModals.test.js.snap +++ /dev/null @@ -1,25 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ForceUnlockModal render 1`] = ` - -`; - -exports[`UnlockModal render 1`] = ` - -`; diff --git a/webpack/ForemanTasks/Components/TaskDetails/Components/Task.js b/webpack/ForemanTasks/Components/TaskDetails/Components/Task.js index c0dc0956b..e76c8f0f9 100644 --- a/webpack/ForemanTasks/Components/TaskDetails/Components/Task.js +++ b/webpack/ForemanTasks/Components/TaskDetails/Components/Task.js @@ -1,7 +1,11 @@ -import React from 'react'; +import React, { useState } from 'react'; import { Grid, Row } from 'patternfly-react'; +import PropTypes from 'prop-types'; import TaskInfo from './TaskInfo'; -import { ForceUnlockModal, UnlockModal } from '../../TaskActions/UnlockModals'; +import { + ForceUnlockConfirmationModal, + UnlockConfirmationModal, +} from '../../common/ClickConfirmation'; import { TaskButtons } from './TaskButtons'; const Task = props => { @@ -25,13 +29,29 @@ const Task = props => { } unlockTaskRequest(id, action); }; + const [unlockModalOpen, setUnlockModalOpen] = useState(false); + const [forceUnlockModalOpen, setForceUnlockModalOpen] = useState(false); + return ( - - + setUnlockModalOpen(false)} + /> + setForceUnlockModalOpen(false)} + /> - + @@ -40,13 +60,21 @@ const Task = props => { }; Task.propTypes = { - ...TaskInfo.PropTypes, - ...TaskButtons.PropTypes, + taskReload: PropTypes.bool, + id: PropTypes.string, + forceCancelTaskRequest: PropTypes.func, + unlockTaskRequest: PropTypes.func, + action: PropTypes.string, + taskReloadStart: PropTypes.func, }; Task.defaultProps = { - ...TaskInfo.defaultProps, - ...TaskButtons.defaultProps, + taskReload: false, + id: '', + forceCancelTaskRequest: () => null, + unlockTaskRequest: () => null, + action: '', + taskReloadStart: () => null, }; export default Task; diff --git a/webpack/ForemanTasks/Components/TaskDetails/Components/TaskButtons.js b/webpack/ForemanTasks/Components/TaskDetails/Components/TaskButtons.js index ed54fa5a4..d8d0462f6 100644 --- a/webpack/ForemanTasks/Components/TaskDetails/Components/TaskButtons.js +++ b/webpack/ForemanTasks/Components/TaskDetails/Components/TaskButtons.js @@ -1,12 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Col, Button } from 'patternfly-react'; -import { useForemanModal } from 'foremanReact/components/ForemanModal/ForemanModalHooks'; import { translate as __ } from 'foremanReact/common/I18n'; -import { - UNLOCK_MODAL, - FORCE_UNLOCK_MODAL, -} from '../../TaskActions/TaskActionsConstants'; export const TaskButtons = ({ canEdit, @@ -24,13 +19,9 @@ export const TaskButtons = ({ parentTask, cancelTaskRequest, resumeTaskRequest, + setUnlockModalOpen, + setForceUnlockModalOpen, }) => { - const unlockModalActions = useForemanModal({ - id: UNLOCK_MODAL, - }); - const forceUnlockModalActions = useForemanModal({ - id: FORCE_UNLOCK_MODAL, - }); const editActionsTitle = canEdit ? undefined : __('You do not have permission'); @@ -114,7 +105,9 @@ export const TaskButtons = ({ className="unlock-button" bsSize="small" disabled={!canEdit || state !== 'paused'} - onClick={unlockModalActions.setModalOpen} + onClick={() => { + setUnlockModalOpen(true); + }} title={editActionsTitle} data-original-title={editActionsTitle} > @@ -124,7 +117,7 @@ export const TaskButtons = ({ className="force-unlock-button" bsSize="small" disabled={!canEdit || state === 'stopped'} - onClick={forceUnlockModalActions.setModalOpen} + onClick={() => setForceUnlockModalOpen(true)} title={editActionsTitle} data-original-title={editActionsTitle} > @@ -134,7 +127,7 @@ export const TaskButtons = ({ ); }; -TaskButtons.propTypes = { +export const TaskButtonspropTypes = { canEdit: PropTypes.bool, dynflowEnableConsole: PropTypes.bool, taskReloadStart: PropTypes.func.isRequired, @@ -151,8 +144,7 @@ TaskButtons.propTypes = { cancelTaskRequest: PropTypes.func, resumeTaskRequest: PropTypes.func, }; - -TaskButtons.defaultProps = { +export const TaskButtonsdefaultProps = { canEdit: false, dynflowEnableConsole: false, taskReload: false, @@ -166,3 +158,14 @@ TaskButtons.defaultProps = { cancelTaskRequest: () => null, resumeTaskRequest: () => null, }; + +TaskButtons.propTypes = { + ...TaskButtonspropTypes, + setUnlockModalOpen: PropTypes.func, + setForceUnlockModalOpen: PropTypes.func, +}; +TaskButtons.defaultProps = { + setUnlockModalOpen: () => null, + setForceUnlockModalOpen: () => null, + ...TaskButtonsdefaultProps, +}; diff --git a/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskButtons.test.js b/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskButtons.test.js index 25bc966c5..0f670fb64 100644 --- a/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskButtons.test.js +++ b/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskButtons.test.js @@ -1,45 +1,160 @@ import React from 'react'; -import { - testComponentSnapshotsWithFixtures, - mount, - shallow, -} from '@theforeman/test'; -import { useForemanModal } from 'foremanReact/components/ForemanModal/ForemanModalHooks'; +import { render, screen, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom'; import { STATUS } from 'foremanReact/constants'; import { TaskButtons } from '../TaskButtons'; -import { - UNLOCK_MODAL, - FORCE_UNLOCK_MODAL, -} from '../../../TaskActions/TaskActionsConstants'; - -const fixtures = { - 'render with minimal Props': { - id: 'test', - taskReloadStart: jest.fn(), - taskProgressToggle: jest.fn(), - }, - 'render with some Props': { - id: 'test', - state: 'paused', - hasSubTasks: true, - dynflowEnableConsole: true, - parentTask: 'parent-id', - taskReload: true, - canEdit: true, - status: STATUS.RESOLVED, - taskReloadStart: jest.fn(), - taskProgressToggle: jest.fn(), - }, + +const setUnlockModalOpen = jest.fn(); +const setForceUnlockModalOpen = jest.fn(); + +const defaultProps = { + id: 'test', + taskReloadStart: jest.fn(), + taskProgressToggle: jest.fn(), + cancelTaskRequest: jest.fn(), + resumeTaskRequest: jest.fn(), + setUnlockModalOpen, + setForceUnlockModalOpen, }; -describe('Task', () => { - describe('rendering', () => - testComponentSnapshotsWithFixtures(TaskButtons, fixtures)); - describe('click test', () => { - const setModalOpen = jest.fn(); - useForemanModal.mockImplementation(id => ({ - setModalOpen: () => setModalOpen(id), - })); +describe('TaskButtons', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('rendering', () => { + it('renders reload button with correct text when taskReload is false', () => { + render(); + expect( + screen.getByRole('button', { name: /start auto-reloading/i }) + ).toBeInTheDocument(); + expect( + screen.queryByRole('button', { name: /stop auto-reloading/i }) + ).not.toBeInTheDocument(); + }); + + it('renders reload button with correct text when taskReload is true', () => { + render(); + expect( + screen.getByRole('button', { name: /stop auto-reloading/i }) + ).toBeInTheDocument(); + expect( + screen.queryByRole('button', { name: /start auto-reloading/i }) + ).not.toBeInTheDocument(); + }); + + it('renders dynflow console link with correct href when externalId is provided', () => { + render(); + const dynflowLink = screen.getByRole('link', { + name: /dynflow console/i, + }); + expect(dynflowLink).toHaveAttribute( + 'href', + '/foreman_tasks/dynflow/external-123' + ); + expect(dynflowLink).toHaveAttribute('target', '_blank'); + expect(dynflowLink).toHaveAttribute('rel', 'noopener noreferrer'); + }); + + it('disables dynflow console link when dynflowEnableConsole is false', () => { + render(); + const dynflowLink = screen.getByRole('link', { + name: /dynflow console/i, + }); + expect(dynflowLink).toHaveClass('disabled'); + }); + + it('enables dynflow console link when dynflowEnableConsole is true', () => { + render( + + ); + const dynflowLink = screen.getByRole('link', { + name: /dynflow console/i, + }); + expect(dynflowLink).not.toHaveClass('disabled'); + }); + + it('disables resume and cancel buttons when canEdit is false', () => { + render(); + expect(screen.getByRole('button', { name: /resume/i })).toBeDisabled(); + expect(screen.getByRole('button', { name: /cancel/i })).toBeDisabled(); + }); + + it('disables resume button when resumable is false', () => { + render(); + expect(screen.getByRole('button', { name: /resume/i })).toBeDisabled(); + }); + + it('disables cancel button when cancellable is false', () => { + render(); + expect(screen.getByRole('button', { name: /cancel/i })).toBeDisabled(); + }); + + it('disables unlock button when state is not paused', () => { + render(); + expect(screen.getByRole('button', { name: /^unlock$/i })).toBeDisabled(); + }); + + it('enables unlock button when state is paused and canEdit is true', () => { + render(); + expect( + screen.getByRole('button', { name: /^unlock$/i }) + ).not.toBeDisabled(); + }); + + it('disables force unlock button when state is stopped', () => { + render(); + expect( + screen.getByRole('button', { name: /force unlock/i }) + ).toBeDisabled(); + }); + + it('enables force unlock button when state is not stopped and canEdit is true', () => { + render(); + expect( + screen.getByRole('button', { name: /force unlock/i }) + ).not.toBeDisabled(); + }); + + it('renders parent task button when parentTask is provided', () => { + render(); + const parentButton = screen.getByRole('link', { name: /parent task/i }); + expect(parentButton).toBeInTheDocument(); + expect(parentButton).toHaveAttribute( + 'href', + '/foreman_tasks/tasks/parent-123' + ); + }); + + it('does not render parent task button when parentTask is not provided', () => { + render(); + expect( + screen.queryByRole('link', { name: /parent task/i }) + ).not.toBeInTheDocument(); + }); + + it('renders sub tasks button when hasSubTasks is true', () => { + render(); + const subTasksButton = screen.getByRole('link', { name: /sub tasks/i }); + expect(subTasksButton).toBeInTheDocument(); + expect(subTasksButton).toHaveAttribute( + 'href', + '/foreman_tasks/tasks/task-123/sub_tasks' + ); + }); + + it('does not render sub tasks button when hasSubTasks is false', () => { + render(); + expect( + screen.queryByRole('link', { name: /sub tasks/i }) + ).not.toBeInTheDocument(); + }); + }); + describe('user interactions', () => { const cancelTaskRequest = jest.fn(); const resumeTaskRequest = jest.fn(); const taskProgressToggle = jest.fn(); @@ -47,6 +162,7 @@ describe('Task', () => { const id = 'some-id'; const action = 'some-action'; const props = { + ...defaultProps, taskReload: false, id, action, @@ -55,41 +171,51 @@ describe('Task', () => { taskProgressToggle, taskReloadStart, status: STATUS.RESOLVED, + canEdit: true, + resumable: true, + cancellable: true, + state: 'paused', }; - afterEach(() => { - jest.clearAllMocks(); - }); - it('reload', () => { - const component = mount(); - const reloadButton = component.find('.reload-button').at(0); - reloadButton.simulate('click'); - expect(taskProgressToggle).toBeCalled(); - }); - it('resume', () => { - const component = shallow(); - const resumeButton = component.find('.resume-button').at(0); - resumeButton.props().onClick(); - expect(taskReloadStart).toBeCalled(); - expect(resumeTaskRequest).toBeCalledWith(id, action); - }); - it('cancel', () => { - const component = shallow(); - const cancelButton = component.find('.cancel-button').at(0); - cancelButton.props().onClick(); - expect(taskReloadStart).toBeCalled(); - expect(cancelTaskRequest).toBeCalledWith(id, action); - }); - it('unlock', () => { - const component = shallow(); - const unlockButton = component.find('.unlock-button').at(0); - unlockButton.props().onClick(); - expect(setModalOpen).toBeCalledWith({ id: UNLOCK_MODAL }); - }); - it('focrce unlock', () => { - const component = shallow(); - const forceUnlockButton = component.find('.force-unlock-button').at(0); - forceUnlockButton.props().onClick(); - expect(setModalOpen).toBeCalledWith({ id: FORCE_UNLOCK_MODAL }); + + it('calls taskProgressToggle when reload button is clicked', () => { + render(); + const reloadButton = screen.getByRole('button', { + name: /start auto-reloading/i, + }); + fireEvent.click(reloadButton); + expect(taskProgressToggle).toHaveBeenCalled(); + }); + + it('calls taskReloadStart and resumeTaskRequest when resume button is clicked', () => { + render(); + const resumeButton = screen.getByRole('button', { name: /resume/i }); + fireEvent.click(resumeButton); + expect(taskReloadStart).toHaveBeenCalledWith(id); + expect(resumeTaskRequest).toHaveBeenCalledWith(id, action); + }); + + it('calls taskReloadStart and cancelTaskRequest when cancel button is clicked', () => { + render(); + const cancelButton = screen.getByRole('button', { name: /cancel/i }); + fireEvent.click(cancelButton); + expect(taskReloadStart).toHaveBeenCalledWith(id); + expect(cancelTaskRequest).toHaveBeenCalledWith(id, action); + }); + + it('calls setUnlockModalOpen when unlock button is clicked', () => { + render(); + const unlockButton = screen.getByRole('button', { name: /^unlock$/i }); + fireEvent.click(unlockButton); + expect(setUnlockModalOpen).toHaveBeenCalledWith(true); + }); + + it('calls setForceUnlockModalOpen when force unlock button is clicked', () => { + render(); + const forceUnlockButton = screen.getByRole('button', { + name: /force unlock/i, + }); + fireEvent.click(forceUnlockButton); + expect(setForceUnlockModalOpen).toHaveBeenCalledWith(true); }); }); }); diff --git a/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/Task.test.js.snap b/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/Task.test.js.snap index 1c63e1ed0..8479276ca 100644 --- a/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/Task.test.js.snap +++ b/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/Task.test.js.snap @@ -2,14 +2,18 @@ exports[`Task rendering render with minimal Props 1`] = ` - - @@ -83,14 +73,18 @@ exports[`Task rendering render with minimal Props 1`] = ` exports[`Task rendering render with some Props 1`] = ` - - diff --git a/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/TaskButtons.test.js.snap b/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/TaskButtons.test.js.snap deleted file mode 100644 index d28b333b0..000000000 --- a/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/TaskButtons.test.js.snap +++ /dev/null @@ -1,212 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Task rendering render with minimal Props 1`] = ` - - - - Start auto-reloading - - - - Dynflow console - - - - Resume - - - Cancel - - - Unlock - - - Force Unlock - - -`; - -exports[`Task rendering render with some Props 1`] = ` - - - - Stop auto-reloading - - - - Dynflow console - - - - Resume - - - Cancel - - - Parent task - - - Sub tasks - - - Unlock - - - Force Unlock - - -`; diff --git a/webpack/ForemanTasks/Components/TaskDetails/__tests__/__snapshots__/TaskDetails.test.js.snap b/webpack/ForemanTasks/Components/TaskDetails/__tests__/__snapshots__/TaskDetails.test.js.snap index 2a54ac567..64814a90c 100644 --- a/webpack/ForemanTasks/Components/TaskDetails/__tests__/__snapshots__/TaskDetails.test.js.snap +++ b/webpack/ForemanTasks/Components/TaskDetails/__tests__/__snapshots__/TaskDetails.test.js.snap @@ -89,34 +89,20 @@ exports[`TaskDetails rendering render with min Props 1`] = ` > { - if ([FORCE_UNLOCK_MODAL, FORCE_UNLOCK_SELECTED_MODAL].includes(actionType)) { - return ( - { - props[actionType]({ url, query, parentTaskID }); - setModalClosed(); - }} - id={id} - selectedRowsLen={selectedRowsLen} - /> - ); - } - return ( - - {sprintf( - __( - `This will %(action)s %(number)s task(s), putting them in the %(state)s state. Are you sure?` - ), - { action: actionText, number: selectedRowsLen, state: actionState } - )} - - {__('No')} - { - props[actionType]({ url, query, parentTaskID }); - setModalClosed(); - }} - > - {__('Yes')} - - - - ); -}; - -ConfirmModal.propTypes = { - actionText: PropTypes.string, - actionState: PropTypes.string, - selectedRowsLen: PropTypes.number.isRequired, - actionType: PropTypes.string, - id: PropTypes.string.isRequired, - parentTaskID: PropTypes.string, - url: PropTypes.string.isRequired, - uriQuery: PropTypes.object, - setModalClosed: PropTypes.func.isRequired, -}; - -ConfirmModal.defaultProps = { - actionType: '', - actionText: '', - actionState: '', - parentTaskID: '', - uriQuery: {}, -}; - -export default ConfirmModal; diff --git a/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/ConfirmModalActions.js b/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/ConfirmModalActions.js deleted file mode 100644 index 78789b8bb..000000000 --- a/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/ConfirmModalActions.js +++ /dev/null @@ -1,106 +0,0 @@ -import { - resumeTask, - cancelTask, - forceCancelTask, -} from '../../TasksTableActions'; -import { - bulkCancelBySearch, - bulkCancelById, - bulkResumeBySearch, - bulkResumeById, - bulkForceCancelBySearch, - bulkForceCancelById, -} from '../../TasksBulkActions'; -import { selectClicked, selectSelectedTasks } from './ConfirmModalSelectors'; -import { selectAllRowsSelected } from '../../TasksTableSelectors'; -import { - CANCEL_MODAL, - RESUME_MODAL, - CANCEL_SELECTED_MODAL, - RESUME_SELECTED_MODAL, - FORCE_UNLOCK_SELECTED_MODAL, -} from '../../TasksTableConstants'; -import { FORCE_UNLOCK_MODAL } from '../../../TaskActions/TaskActionsConstants'; - -export default { - [CANCEL_SELECTED_MODAL]: ({ url, query, parentTaskID }) => ( - dispatch, - getState - ) => { - if (selectAllRowsSelected(getState())) { - return dispatch(bulkCancelBySearch({ query, parentTaskID })); - } - return dispatch( - bulkCancelById({ - selected: selectSelectedTasks(getState()), - url, - parentTaskID, - }) - ); - }, - [CANCEL_MODAL]: ({ url, parentTaskID }) => (dispatch, getState) => { - const { taskId, taskName } = selectClicked(getState()); - return dispatch( - cancelTask({ - taskId, - taskName, - url, - parentTaskID, - }) - ); - }, - [RESUME_SELECTED_MODAL]: ({ url, query, parentTaskID }) => ( - dispatch, - getState - ) => { - if (selectAllRowsSelected(getState())) { - return dispatch(bulkResumeBySearch({ query, parentTaskID })); - } - return dispatch( - bulkResumeById({ - selected: selectSelectedTasks(getState()), - url, - parentTaskID, - }) - ); - }, - - [RESUME_MODAL]: ({ url, parentTaskID }) => (dispatch, getState) => { - const { taskId, taskName } = selectClicked(getState()); - return dispatch( - resumeTask({ - taskId, - taskName, - url, - parentTaskID, - }) - ); - }, - - [FORCE_UNLOCK_MODAL]: ({ url, parentTaskID }) => (dispatch, getState) => { - const { taskId, taskName } = selectClicked(getState()); - return dispatch( - forceCancelTask({ - taskId, - taskName, - url, - parentTaskID, - }) - ); - }, - [FORCE_UNLOCK_SELECTED_MODAL]: ({ url, query, parentTaskID }) => ( - dispatch, - getState - ) => { - if (selectAllRowsSelected(getState())) { - return dispatch(bulkForceCancelBySearch({ query, parentTaskID })); - } - return dispatch( - bulkForceCancelById({ - selected: selectSelectedTasks(getState()), - url, - parentTaskID, - }) - ); - }, -}; diff --git a/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/ConfirmModalReducer.js b/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/ConfirmModalReducer.js deleted file mode 100644 index eb7eaeb6e..000000000 --- a/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/ConfirmModalReducer.js +++ /dev/null @@ -1,38 +0,0 @@ -import Immutable from 'seamless-immutable'; -import { - UPDATE_MODAL, - CANCEL_SELECTED_MODAL, - RESUME_SELECTED_MODAL, - RESUME_MODAL, - CANCEL_MODAL, -} from '../../TasksTableConstants'; - -const initialState = Immutable({}); - -export const ConfirmModalReducer = (state = initialState, action) => { - const { type, payload } = action; - switch (type) { - case UPDATE_MODAL: - switch (payload.modalID) { - case CANCEL_SELECTED_MODAL: - case CANCEL_MODAL: - return state.merge({ - actionText: 'cancel', - actionState: 'stopped', - actionType: payload.modalID, - }); - case RESUME_SELECTED_MODAL: - case RESUME_MODAL: - return state.merge({ - actionText: 'resume', - actionState: 'running', - actionType: payload.modalID, - }); - default: - return state.set('actionType', payload.modalID); - } - default: - return state; - } -}; -export default ConfirmModalReducer; diff --git a/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/ConfirmModalSelectors.js b/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/ConfirmModalSelectors.js index 80d03109a..cd63508f5 100644 --- a/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/ConfirmModalSelectors.js +++ b/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/ConfirmModalSelectors.js @@ -1,4 +1,3 @@ -import { selectForemanTasks } from '../../../../ForemanTasksSelectors'; import { selectTasksTableQuery, selectResults, @@ -6,15 +5,7 @@ import { selectItemCount, selectAllRowsSelected, } from '../../TasksTableSelectors'; -import { RESUME_MODAL, CANCEL_MODAL } from '../../TasksTableConstants'; -import { FORCE_UNLOCK_MODAL } from '../../../TaskActions/TaskActionsConstants'; -export const selectCofirmModal = state => - selectForemanTasks(state).confirmModal || {}; - -export const selectActionType = state => selectCofirmModal(state).actionType; -export const selectActionText = state => selectCofirmModal(state).actionText; -export const selectActionState = state => selectCofirmModal(state).actionState; export const selectClicked = state => selectTasksTableQuery(state).clicked || {}; @@ -32,13 +23,6 @@ export const selectSelectedTasks = state => { }; export const selectSelectedRowsLen = state => { - if ( - [CANCEL_MODAL, RESUME_MODAL, FORCE_UNLOCK_MODAL].includes( - selectActionType(state) - ) - ) { - return 1; - } if (selectAllRowsSelected(state)) { return selectItemCount(state); } diff --git a/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/GenericConfirmModal.js b/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/GenericConfirmModal.js new file mode 100644 index 000000000..90f378b21 --- /dev/null +++ b/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/GenericConfirmModal.js @@ -0,0 +1,70 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { useDispatch } from 'react-redux'; +import { translate as __ } from 'foremanReact/common/I18n'; +import { Button, Modal, ModalVariant } from '@patternfly/react-core'; + +export const GenericConfirmModal = ({ + isModalOpen, + setIsModalOpen, + title, + message, + onConfirm, + confirmButtonVariant, + ouiaIdPrefix, +}) => { + const dispatch = useDispatch(); + + const handleConfirm = () => { + const action = onConfirm(); + if (action) { + dispatch(action); + } + setIsModalOpen(false); + }; + + return ( + setIsModalOpen(false)} + ouiaId={`${ouiaIdPrefix}-modal`} + actions={[ + + {__('Yes')} + , + setIsModalOpen(false)} + > + {__('No')} + , + ]} + > + {message} + + ); +}; + +GenericConfirmModal.propTypes = { + isModalOpen: PropTypes.bool.isRequired, + setIsModalOpen: PropTypes.func.isRequired, + title: PropTypes.string.isRequired, + message: PropTypes.string.isRequired, + onConfirm: PropTypes.func.isRequired, + confirmButtonVariant: PropTypes.string, + ouiaIdPrefix: PropTypes.string.isRequired, +}; + +GenericConfirmModal.defaultProps = { + confirmButtonVariant: 'primary', +}; diff --git a/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/ConfirmModal.test.js b/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/ConfirmModal.test.js deleted file mode 100644 index ff32ffb22..000000000 --- a/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/ConfirmModal.test.js +++ /dev/null @@ -1,36 +0,0 @@ -import { testComponentSnapshotsWithFixtures } from '@theforeman/test'; -import { ConfirmModal } from '../ConfirmModal'; -import { RESUME_SELECTED_MODAL } from '../../../TasksTableConstants'; -import { FORCE_UNLOCK_MODAL } from '../../../../TaskActions/TaskActionsConstants'; - -const fixtures = { - 'renders ConfirmModal': { - actionType: RESUME_SELECTED_MODAL, - actionText: 'some text', - actionState: 'some state', - selectedRowsLen: 1, - id: 'modalID', - parentTaskID: 'parent-id', - allRowsSelected: true, - url: 'some-url', - uriQuery: { state: 'stopped' }, - setModalClosed: jest.fn(), - }, - - 'renders ConfirmModal for unlock ': { - actionType: FORCE_UNLOCK_MODAL, - actionText: 'some text', - actionState: 'some state', - selectedRowsLen: 1, - id: 'modalID', - parentTaskID: 'parent-id', - allRowsSelected: true, - url: 'some-url', - uriQuery: { state: 'stopped' }, - setModalClosed: jest.fn(), - }, -}; - -describe('ConfirmModal', () => { - testComponentSnapshotsWithFixtures(ConfirmModal, fixtures); -}); diff --git a/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/ConfirmModalActions.test.js b/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/ConfirmModalActions.test.js deleted file mode 100644 index c15beb25a..000000000 --- a/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/ConfirmModalActions.test.js +++ /dev/null @@ -1,205 +0,0 @@ -import taskActions from '../ConfirmModalActions'; -import { - CANCEL_MODAL, - RESUME_MODAL, - CANCEL_SELECTED_MODAL, - RESUME_SELECTED_MODAL, - FORCE_UNLOCK_SELECTED_MODAL, -} from '../../../TasksTableConstants'; - -import { FORCE_UNLOCK_MODAL } from '../../../../TaskActions/TaskActionsConstants'; - -import { - resumeTask, - cancelTask, - forceCancelTask, -} from '../../../TasksTableActions'; -import { - bulkCancelBySearch, - bulkCancelById, - bulkResumeBySearch, - bulkResumeById, - bulkForceCancelBySearch, - bulkForceCancelById, -} from '../../../TasksBulkActions'; - -jest.mock('../../../TasksBulkActions'); -jest.mock('../../../TasksTableActions'); - -const bulkCancelBySearchMock = 'bulkCancelBySearchMock'; -const bulkCancelByIdMock = 'bulkCancelByIdMock'; -const bulkResumeBySearchMock = 'bulkResumeBySearchMock'; -const bulkResumeByIdMock = 'bulkResumeByIdMock'; -const bulkForceCancelBySearchMock = 'bulkForceCancelBySearchMock'; -const bulkForceCancelByIdMock = 'bulkForceCancelByIdMock'; -const resumeTaskMock = 'resumeTaskMock'; -const cancelTaskMock = 'cancelTaskMock'; -const forceCancelTaskMock = 'forceCancelTaskMock'; - -bulkCancelBySearch.mockImplementation(() => bulkCancelBySearchMock); -bulkCancelById.mockImplementation(() => bulkCancelByIdMock); -bulkResumeBySearch.mockImplementation(() => bulkResumeBySearchMock); -bulkResumeById.mockImplementation(() => bulkResumeByIdMock); -bulkForceCancelBySearch.mockImplementation(() => bulkForceCancelBySearchMock); -bulkForceCancelById.mockImplementation(() => bulkForceCancelByIdMock); -resumeTask.mockImplementation(() => resumeTaskMock); -cancelTask.mockImplementation(() => cancelTaskMock); -forceCancelTask.mockImplementation(() => forceCancelTaskMock); - -const url = 'some-url'; -const query = 'some-query'; -const parentTaskID = 'some-parentTaskID'; - -const runWithGetState = (state, action, dispatch, ...params) => { - const getState = () => state; - action(...params)(dispatch, getState); -}; - -const clickedState = { - foremanTasks: { - tasksTable: { - tasksTableQuery: { - clicked: { taskId: 'some-id', taskName: 'some-name' }, - }, - }, - }, -}; - -const selectedState = { - foremanTasks: { - tasksTable: { - tasksTableQuery: { - allRowsSelected: false, - selectedRows: [1, 2, 3], - }, - tasksTableContent: { - results: [ - { - id: 1, - action: 'action1', - available_actions: { cancellable: true }, - }, - { - id: 2, - action: 'action2', - available_actions: { resumable: true }, - }, - ], - }, - }, - }, -}; -const allRowsState = { - foremanTasks: { - tasksTable: { - tasksTableQuery: { - allRowsSelected: true, - }, - }, - }, -}; -describe('ConfirmModalActions', () => { - const dispatch = jest.fn(); - - beforeEach(() => dispatch.mockClear()); - it('run CANCEL_MODAL', () => { - runWithGetState(clickedState, taskActions[CANCEL_MODAL], dispatch, { - url, - parentTaskID, - }); - expect(dispatch).toBeCalledWith(cancelTaskMock); - }); - it('run RESUME_MODAL', () => { - runWithGetState(clickedState, taskActions[RESUME_MODAL], dispatch, { - url, - parentTaskID, - }); - expect(dispatch).toBeCalledWith(resumeTaskMock); - }); - it('run FORCE_UNLOCK_MODAL', () => { - runWithGetState(clickedState, taskActions[FORCE_UNLOCK_MODAL], dispatch, { - url, - parentTaskID, - }); - expect(dispatch).toBeCalledWith(forceCancelTaskMock); - }); - it('run CANCEL_SELECTED_MODAL by id', () => { - runWithGetState( - selectedState, - taskActions[CANCEL_SELECTED_MODAL], - dispatch, - { - url, - query, - parentTaskID, - } - ); - expect(dispatch).toBeCalledWith(bulkCancelByIdMock); - }); - - it('run CANCEL_SELECTED_MODAL by search', () => { - runWithGetState( - allRowsState, - taskActions[CANCEL_SELECTED_MODAL], - dispatch, - { - url, - query, - parentTaskID, - } - ); - expect(dispatch).toBeCalledWith(bulkCancelBySearchMock); - }); - it('run RESUME_SELECTED_MODAL by id', () => { - runWithGetState( - selectedState, - taskActions[RESUME_SELECTED_MODAL], - dispatch, - { - url, - query, - parentTaskID, - } - ); - expect(dispatch).toBeCalledWith(bulkResumeByIdMock); - }); - it('run RESUME_SELECTED_MODAL by search', () => { - runWithGetState( - allRowsState, - taskActions[RESUME_SELECTED_MODAL], - dispatch, - { - url, - query, - parentTaskID, - } - ); - expect(dispatch).toBeCalledWith(bulkResumeBySearchMock); - }); - it('run FORCE_UNLOCK_SELECTED_MODAL by id', () => { - runWithGetState( - selectedState, - taskActions[FORCE_UNLOCK_SELECTED_MODAL], - dispatch, - { - url, - query, - parentTaskID, - } - ); - expect(dispatch).toBeCalledWith(bulkForceCancelByIdMock); - }); - it('run FORCE_UNLOCK_SELECTED_MODAL by search', () => { - runWithGetState( - allRowsState, - taskActions[FORCE_UNLOCK_SELECTED_MODAL], - dispatch, - { - url, - query, - parentTaskID, - } - ); - expect(dispatch).toBeCalledWith(bulkForceCancelBySearchMock); - }); -}); diff --git a/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/ConfirmModalReducer.test.js b/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/ConfirmModalReducer.test.js deleted file mode 100644 index 8453ba2b3..000000000 --- a/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/ConfirmModalReducer.test.js +++ /dev/null @@ -1,27 +0,0 @@ -import { testReducerSnapshotWithFixtures } from '@theforeman/test'; -import { - UPDATE_MODAL, - CANCEL_MODAL, - RESUME_SELECTED_MODAL, -} from '../../../TasksTableConstants'; - -import reducer from '../ConfirmModalReducer'; - -const fixtures = { - 'should return the initial state': {}, - 'should handle UPDATE_MODAL to cancel': { - action: { - type: UPDATE_MODAL, - payload: { modalID: CANCEL_MODAL }, - }, - }, - 'should handle UPDATE_MODAL to resume': { - action: { - type: UPDATE_MODAL, - payload: { modalID: RESUME_SELECTED_MODAL }, - }, - }, -}; - -describe('ConfirmModalReducer reducer', () => - testReducerSnapshotWithFixtures(reducer, fixtures)); diff --git a/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/ConfirmModalSelectors.test.js b/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/ConfirmModalSelectors.test.js index f0c2da63a..1d1c195c4 100644 --- a/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/ConfirmModalSelectors.test.js +++ b/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/ConfirmModalSelectors.test.js @@ -1,8 +1,6 @@ import { testSelectorsSnapshotWithFixtures } from '@theforeman/test'; import { - selectActionText, - selectActionState, - selectActionType, + selectClicked, selectSelectedTasks, selectSelectedRowsLen, } from '../ConfirmModalSelectors'; @@ -10,11 +8,6 @@ import { CANCEL_MODAL } from '../../../TasksTableConstants'; const state = { foremanTasks: { - confirmModal: { - actionText: 'some-text', - actionState: 'some-state', - actionType: 'some-type', - }, tasksTable: { tasksTableContent: { results: [ @@ -28,26 +21,44 @@ const state = { ], itemCount: 10, }, - tasksTableQuery: { selectedRows: [1, 2, 3] }, + tasksTableQuery: { + selectedRows: [1, 2, 3], + clicked: { taskId: '1', taskName: 'test-task' }, + allRowsSelected: false, + }, }, }, }; const fixtures = { - 'should select actionText': () => selectActionText(state), - 'should select actionState': () => selectActionState(state), - 'should select actionType': () => selectActionType(state), + 'should select clicked': () => selectClicked(state), 'should select selectedTasks': () => selectSelectedTasks(state), 'should select selectedRowsLen 1': () => selectSelectedRowsLen({ ...state, - foremanTasks: { confirmModal: { actionType: CANCEL_MODAL } }, + foremanTasks: { + tasksTable: { + ...state.foremanTasks.tasksTable, + tasksTableQuery: { + ...state.foremanTasks.tasksTable.tasksTableQuery, + clicked: { modalType: CANCEL_MODAL }, + }, + }, + }, }), 'should select selectedRowsLen all': () => selectSelectedRowsLen(state), 'should select selectedRowsLen some': () => selectSelectedRowsLen({ ...state, - tasksTable: { tasksTableQuery: { allRowsSelected: true } }, + foremanTasks: { + tasksTable: { + ...state.foremanTasks.tasksTable, + tasksTableQuery: { + ...state.foremanTasks.tasksTable.tasksTableQuery, + allRowsSelected: true, + }, + }, + }, }), }; diff --git a/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/__snapshots__/ConfirmModal.test.js.snap b/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/__snapshots__/ConfirmModal.test.js.snap deleted file mode 100644 index 58415dccb..000000000 --- a/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/__snapshots__/ConfirmModal.test.js.snap +++ /dev/null @@ -1,41 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ConfirmModal renders ConfirmModal 1`] = ` - - This will some text 1 task(s), putting them in the some state state. Are you sure? - - - No - - - Yes - - - -`; - -exports[`ConfirmModal renders ConfirmModal for unlock 1`] = ` - -`; diff --git a/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/__snapshots__/ConfirmModalReducer.test.js.snap b/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/__snapshots__/ConfirmModalReducer.test.js.snap deleted file mode 100644 index e6d38d6a6..000000000 --- a/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/__snapshots__/ConfirmModalReducer.test.js.snap +++ /dev/null @@ -1,19 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ConfirmModalReducer reducer should handle UPDATE_MODAL to cancel 1`] = ` -Object { - "actionState": "stopped", - "actionText": "cancel", - "actionType": "cancelConfirmModal", -} -`; - -exports[`ConfirmModalReducer reducer should handle UPDATE_MODAL to resume 1`] = ` -Object { - "actionState": "running", - "actionText": "resume", - "actionType": "resumeSelectedConfirmModal", -} -`; - -exports[`ConfirmModalReducer reducer should return the initial state 1`] = `Object {}`; diff --git a/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/__snapshots__/ConfirmModalSelectors.test.js.snap b/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/__snapshots__/ConfirmModalSelectors.test.js.snap index 419195330..64991e565 100644 --- a/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/__snapshots__/ConfirmModalSelectors.test.js.snap +++ b/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/__snapshots__/ConfirmModalSelectors.test.js.snap @@ -1,16 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`TasksDashboard - Selectors should select actionState 1`] = `"some-state"`; - -exports[`TasksDashboard - Selectors should select actionText 1`] = `"some-text"`; - -exports[`TasksDashboard - Selectors should select actionType 1`] = `"some-type"`; +exports[`TasksDashboard - Selectors should select clicked 1`] = ` +Object { + "taskId": "1", + "taskName": "test-task", +} +`; -exports[`TasksDashboard - Selectors should select selectedRowsLen 1 1`] = `1`; +exports[`TasksDashboard - Selectors should select selectedRowsLen 1 1`] = `3`; exports[`TasksDashboard - Selectors should select selectedRowsLen all 1`] = `3`; -exports[`TasksDashboard - Selectors should select selectedRowsLen some 1`] = `3`; +exports[`TasksDashboard - Selectors should select selectedRowsLen some 1`] = `0`; exports[`TasksDashboard - Selectors should select selectedTasks 1`] = ` Array [ diff --git a/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/index.test.js b/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/index.test.js new file mode 100644 index 000000000..2d46336f3 --- /dev/null +++ b/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/__test__/index.test.js @@ -0,0 +1,409 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { Provider } from 'react-redux'; +import { configureStore } from '@reduxjs/toolkit'; +import { + CancelModal, + ResumeModal, + CancelSelectedModal, + ResumeSelectedModal, + ForceUnlockModal, + ForceUnlockSelectedModal, +} from '../index'; + +// Mock the action creators +jest.mock('../../../TasksTableActions', () => ({ + cancelTask: jest.fn(() => ({ type: 'CANCEL_TASK' })), + resumeTask: jest.fn(() => ({ type: 'RESUME_TASK' })), +})); + +jest.mock('../../../TasksBulkActions', () => ({ + bulkCancelBySearch: jest.fn(() => ({ type: 'BULK_CANCEL_BY_SEARCH' })), + bulkCancelById: jest.fn(() => ({ type: 'BULK_CANCEL_BY_ID' })), + bulkResumeBySearch: jest.fn(() => ({ type: 'BULK_RESUME_BY_SEARCH' })), + bulkResumeById: jest.fn(() => ({ type: 'BULK_RESUME_BY_ID' })), + bulkForceUnlockBySearch: jest.fn(() => ({ + type: 'BULK_FORCE_UNLOCK_BY_SEARCH', + })), + bulkForceUnlockById: jest.fn(() => ({ type: 'BULK_FORCE_UNLOCK_BY_ID' })), +})); + +// Mock the selectors +jest.mock('../ConfirmModalSelectors', () => ({ + selectClicked: jest.fn(() => ({ taskId: '123', taskName: 'Test Task' })), + selectSelectedTasks: jest.fn(() => [ + { id: 1, name: 'Task 1' }, + { id: 2, name: 'Task 2' }, + ]), + selectSelectedRowsLen: jest.fn(() => 2), +})); + +jest.mock('../../../TasksTableSelectors', () => ({ + selectAllRowsSelected: jest.fn(() => false), +})); + +// Create a mock store +const createMockStore = (initialState = {}) => { + return configureStore({ + reducer: { + foremanTasks: (state = initialState, action) => state, + }, + preloadedState: { + foremanTasks: initialState, + }, + }); +}; + +// Test wrapper component +const TestWrapper = ({ children, store }) => ( + {children} +); + +describe('ConfirmModal Components', () => { + const defaultProps = { + isModalOpen: true, + setIsModalOpen: jest.fn(), + url: '/api/tasks', + parentTaskID: 'parent-123', + }; + + const mockStore = createMockStore(); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('CancelModal', () => { + it('renders with correct title and content', () => { + render( + + + + ); + + expect(screen.getByText('Cancel Task')).toBeInTheDocument(); + expect( + screen.getByText( + /This will cancel task "Test Task", putting it in the stopped state/ + ) + ).toBeInTheDocument(); + expect(screen.getByText('No')).toBeInTheDocument(); + expect(screen.getByText('Yes')).toBeInTheDocument(); + }); + + it('calls setIsModalOpen when cancel button is clicked', () => { + const setIsModalOpen = jest.fn(); + + render( + + + + ); + + const cancelButton = screen.getByRole('button', { name: 'No' }); + fireEvent.click(cancelButton); + + expect(setIsModalOpen).toHaveBeenCalledWith(false); + }); + + it('calls setIsModalOpen when confirm button is clicked', () => { + const setIsModalOpen = jest.fn(); + + render( + + + + ); + + const confirmButton = screen.getByRole('button', { name: 'Yes' }); + fireEvent.click(confirmButton); + + expect(setIsModalOpen).toHaveBeenCalledWith(false); + }); + + it('does not render when isModalOpen is false', () => { + render( + + + + ); + + expect(screen.queryByText('Cancel Task')).not.toBeInTheDocument(); + }); + }); + + describe('ResumeModal', () => { + it('renders with correct title and content', () => { + render( + + + + ); + + expect(screen.getByText('Resume Task')).toBeInTheDocument(); + expect( + screen.getByText( + /This will resume task "Test Task", putting it in the running state/ + ) + ).toBeInTheDocument(); + expect(screen.getByText('No')).toBeInTheDocument(); + expect(screen.getByText('Yes')).toBeInTheDocument(); + }); + + it('calls setIsModalOpen when cancel button is clicked', () => { + const setIsModalOpen = jest.fn(); + + render( + + + + ); + + const cancelButton = screen.getByRole('button', { name: 'No' }); + fireEvent.click(cancelButton); + + expect(setIsModalOpen).toHaveBeenCalledWith(false); + }); + + it('calls setIsModalOpen when confirm button is clicked', () => { + const setIsModalOpen = jest.fn(); + + render( + + + + ); + + const confirmButton = screen.getByRole('button', { name: 'Yes' }); + fireEvent.click(confirmButton); + + expect(setIsModalOpen).toHaveBeenCalledWith(false); + }); + }); + + describe('CancelSelectedModal', () => { + const selectedProps = { + ...defaultProps, + uriQuery: { search: 'test' }, + }; + + it('renders with correct title and content', () => { + render( + + + + ); + + expect(screen.getByText('Cancel Selected Tasks')).toBeInTheDocument(); + expect( + screen.getByText( + /This will cancel 2 task\(s\), putting them in the stopped state/ + ) + ).toBeInTheDocument(); + expect(screen.getByText('No')).toBeInTheDocument(); + expect(screen.getByText('Yes')).toBeInTheDocument(); + }); + + it('calls setIsModalOpen when cancel button is clicked', () => { + const setIsModalOpen = jest.fn(); + + render( + + + + ); + + const cancelButton = screen.getByRole('button', { name: 'No' }); + fireEvent.click(cancelButton); + + expect(setIsModalOpen).toHaveBeenCalledWith(false); + }); + + it('calls setIsModalOpen when confirm button is clicked', () => { + const setIsModalOpen = jest.fn(); + + render( + + + + ); + + const confirmButton = screen.getByRole('button', { name: 'Yes' }); + fireEvent.click(confirmButton); + + expect(setIsModalOpen).toHaveBeenCalledWith(false); + }); + }); + + describe('ResumeSelectedModal', () => { + const selectedProps = { + ...defaultProps, + uriQuery: { search: 'test' }, + }; + + it('renders with correct title and content', () => { + render( + + + + ); + + expect(screen.getByText('Resume Selected Tasks')).toBeInTheDocument(); + expect( + screen.getByText( + /This will resume 2 task\(s\), putting them in the running state/ + ) + ).toBeInTheDocument(); + expect(screen.getByText('No')).toBeInTheDocument(); + expect(screen.getByText('Yes')).toBeInTheDocument(); + }); + + it('calls setIsModalOpen when cancel button is clicked', () => { + const setIsModalOpen = jest.fn(); + + render( + + + + ); + + const cancelButton = screen.getByRole('button', { name: 'No' }); + fireEvent.click(cancelButton); + + expect(setIsModalOpen).toHaveBeenCalledWith(false); + }); + }); + + describe('ForceUnlockModal', () => { + it('renders with correct title and content', () => { + render( + + + + ); + + expect(screen.getByText('Force Unlock Task')).toBeInTheDocument(); + expect( + screen.getByText( + /This will force unlock task "Test Task". This may cause harm and should be used with caution/ + ) + ).toBeInTheDocument(); + expect(screen.getByText('No')).toBeInTheDocument(); + expect(screen.getByText('Yes')).toBeInTheDocument(); + }); + + it('calls setIsModalOpen when cancel button is clicked', () => { + const setIsModalOpen = jest.fn(); + + render( + + + + ); + + const cancelButton = screen.getByRole('button', { name: 'No' }); + fireEvent.click(cancelButton); + + expect(setIsModalOpen).toHaveBeenCalledWith(false); + }); + }); + + describe('ForceUnlockSelectedModal', () => { + const selectedProps = { + ...defaultProps, + uriQuery: { search: 'test' }, + }; + + it('renders with correct title and content', () => { + render( + + + + ); + + expect( + screen.getByText('Force Unlock Selected Tasks') + ).toBeInTheDocument(); + expect( + screen.getByText( + /This will force unlock 2 task\(s\). This may cause harm and should be used with caution/ + ) + ).toBeInTheDocument(); + expect(screen.getByText('No')).toBeInTheDocument(); + expect(screen.getByText('Yes')).toBeInTheDocument(); + }); + + it('calls setIsModalOpen when cancel button is clicked', () => { + const setIsModalOpen = jest.fn(); + + render( + + + + ); + + const cancelButton = screen.getByRole('button', { name: 'No' }); + fireEvent.click(cancelButton); + + expect(setIsModalOpen).toHaveBeenCalledWith(false); + }); + }); + + describe('Accessibility', () => { + it('has proper ARIA attributes for all modals', () => { + const { rerender } = render( + + + + ); + + expect(screen.getByRole('dialog')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'No' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Yes' })).toBeInTheDocument(); + + // Test other modals + rerender( + + + + ); + + expect(screen.getByRole('dialog')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'No' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Yes' })).toBeInTheDocument(); + }); + }); + + describe('Modal Visibility', () => { + it('handles modal visibility correctly for all components', () => { + const { rerender } = render( + + + + ); + + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + + rerender( + + + + ); + + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + }); + }); +}); diff --git a/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/createBulkTaskModal.js b/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/createBulkTaskModal.js new file mode 100644 index 000000000..da779f5a5 --- /dev/null +++ b/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/createBulkTaskModal.js @@ -0,0 +1,67 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { useSelector } from 'react-redux'; +import { sprintf } from 'foremanReact/common/I18n'; +import { + selectSelectedTasks, + selectSelectedRowsLen, +} from './ConfirmModalSelectors'; +import { selectAllRowsSelected } from '../../TasksTableSelectors'; +import { GenericConfirmModal } from './GenericConfirmModal'; + +export const createBulkTaskModal = ({ + bulkActionBySearch, + bulkActionById, + title, + messageTemplate, + confirmButtonVariant = 'primary', + ouiaIdPrefix, +}) => { + const BulkTaskModal = ({ + isModalOpen, + setIsModalOpen, + url, + uriQuery, + parentTaskID, + }) => { + const allRowsSelected = useSelector(selectAllRowsSelected); + const selectedTasks = useSelector(selectSelectedTasks); + const selectedRowsLen = useSelector(selectSelectedRowsLen); + + const handleConfirm = () => + allRowsSelected + ? bulkActionBySearch({ query: uriQuery, parentTaskID }) + : bulkActionById({ + selected: selectedTasks, + url, + parentTaskID, + }); + + return ( + + ); + }; + + BulkTaskModal.propTypes = { + isModalOpen: PropTypes.bool.isRequired, + setIsModalOpen: PropTypes.func.isRequired, + url: PropTypes.string.isRequired, + uriQuery: PropTypes.object, + parentTaskID: PropTypes.string, + }; + + BulkTaskModal.defaultProps = { + uriQuery: {}, + parentTaskID: null, + }; + + return BulkTaskModal; +}; diff --git a/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/createTaskModal.js b/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/createTaskModal.js new file mode 100644 index 000000000..d191c3fbe --- /dev/null +++ b/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/createTaskModal.js @@ -0,0 +1,51 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { useSelector } from 'react-redux'; +import { sprintf } from 'foremanReact/common/I18n'; +import { selectClicked } from './ConfirmModalSelectors'; +import { GenericConfirmModal } from './GenericConfirmModal'; + +export const createTaskModal = ({ + actionCreator, + title, + messageTemplate, + confirmButtonVariant = 'primary', + ouiaIdPrefix, +}) => { + const TaskModal = ({ isModalOpen, setIsModalOpen, url, parentTaskID }) => { + const { taskId, taskName } = useSelector(selectClicked); + + const handleConfirm = () => + actionCreator({ + taskId, + taskName, + url, + parentTaskID, + }); + + return ( + + ); + }; + + TaskModal.propTypes = { + isModalOpen: PropTypes.bool.isRequired, + setIsModalOpen: PropTypes.func.isRequired, + url: PropTypes.string.isRequired, + parentTaskID: PropTypes.string, + }; + + TaskModal.defaultProps = { + parentTaskID: null, + }; + + return TaskModal; +}; diff --git a/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/index.js b/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/index.js index 5759e389b..4ddb356f3 100644 --- a/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/index.js +++ b/webpack/ForemanTasks/Components/TasksTable/Components/ConfirmModal/index.js @@ -1,29 +1,79 @@ -import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; -import { ConfirmModal } from './ConfirmModal'; -import reducer from './ConfirmModalReducer'; -import tasksActions from './ConfirmModalActions'; +import { translate as __ } from 'foremanReact/common/I18n'; +import { createTaskModal } from './createTaskModal'; +import { createBulkTaskModal } from './createBulkTaskModal'; import { - selectActionText, - selectActionState, - selectActionType, - selectClicked, - selectSelectedRowsLen, -} from './ConfirmModalSelectors'; -import { selectAllRowsSelected } from '../../TasksTableSelectors'; + cancelTask, + forceCancelTask, + resumeTask, +} from '../../TasksTableActions'; +import { + bulkCancelBySearch, + bulkCancelById, + bulkForceCancelBySearch, + bulkForceCancelById, + bulkResumeBySearch, + bulkResumeById, +} from '../../TasksBulkActions'; + +export const CancelModal = createTaskModal({ + actionCreator: cancelTask, + title: __('Cancel Task'), + messageTemplate: __( + 'This will cancel task "%(taskName)s", putting it in the stopped state. Are you sure?' + ), + confirmButtonVariant: 'primary', + ouiaIdPrefix: 'cancel', +}); + +export const CancelSelectedModal = createBulkTaskModal({ + bulkActionBySearch: bulkCancelBySearch, + bulkActionById: bulkCancelById, + title: __('Cancel Selected Tasks'), + messageTemplate: __( + 'This will cancel %(number)s task(s), putting them in the stopped state. Are you sure?' + ), + confirmButtonVariant: 'primary', + ouiaIdPrefix: 'cancel-selected', +}); -const mapStateToProps = state => ({ - actionText: selectActionText(state), - actionType: selectActionType(state), - actionState: selectActionState(state), - allRowsSelected: selectAllRowsSelected(state), - clicked: selectClicked(state), - selectedRowsLen: selectSelectedRowsLen(state), +export const ForceUnlockModal = createTaskModal({ + actionCreator: forceCancelTask, + title: __('Force Unlock Task'), + messageTemplate: __( + 'This will force unlock task "%(taskName)s". This may cause harm and should be used with caution. Are you sure?' + ), + confirmButtonVariant: 'danger', + ouiaIdPrefix: 'force-unlock', }); -const mapDispatchToProps = dispatch => - bindActionCreators(tasksActions, dispatch); +export const ForceUnlockSelectedModal = createBulkTaskModal({ + bulkActionBySearch: bulkForceCancelBySearch, + bulkActionById: bulkForceCancelById, + title: __('Force Unlock Selected Tasks'), + messageTemplate: __( + 'This will force unlock %(number)s task(s). This may cause harm and should be used with caution. Are you sure?' + ), + confirmButtonVariant: 'danger', + ouiaIdPrefix: 'force-unlock-selected', +}); -export const reducers = { confirmModal: reducer }; +export const ResumeModal = createTaskModal({ + actionCreator: resumeTask, + title: __('Resume Task'), + messageTemplate: __( + 'This will resume task "%(taskName)s", putting it in the running state. Are you sure?' + ), + confirmButtonVariant: 'primary', + ouiaIdPrefix: 'resume', +}); -export default connect(mapStateToProps, mapDispatchToProps)(ConfirmModal); +export const ResumeSelectedModal = createBulkTaskModal({ + bulkActionBySearch: bulkResumeBySearch, + bulkActionById: bulkResumeById, + title: __('Resume Selected Tasks'), + messageTemplate: __( + 'This will resume %(number)s task(s), putting them in the running state. Are you sure?' + ), + confirmButtonVariant: 'primary', + ouiaIdPrefix: 'resume-selected', +}); diff --git a/webpack/ForemanTasks/Components/TasksTable/TasksTable.js b/webpack/ForemanTasks/Components/TasksTable/TasksTable.js index d2da164a1..3dc504e28 100644 --- a/webpack/ForemanTasks/Components/TasksTable/TasksTable.js +++ b/webpack/ForemanTasks/Components/TasksTable/TasksTable.js @@ -8,8 +8,11 @@ import Pagination from 'foremanReact/components/Pagination'; import { getURIQuery } from 'foremanReact/common/helpers'; import createTasksTableSchema from './TasksTableSchema'; import { updateURlQuery } from './TasksTableHelpers'; -import { RESUME_MODAL, CANCEL_MODAL } from './TasksTableConstants'; -import { FORCE_UNLOCK_MODAL } from '../TaskActions/TaskActionsConstants'; +import { + RESUME_MODAL, + CANCEL_MODAL, + FORCE_UNLOCK_MODAL, +} from './TasksTableConstants'; const TasksTable = ({ getTableItems, diff --git a/webpack/ForemanTasks/Components/TasksTable/TasksTableConstants.js b/webpack/ForemanTasks/Components/TasksTable/TasksTableConstants.js index ebe63bd85..1379f032c 100644 --- a/webpack/ForemanTasks/Components/TasksTable/TasksTableConstants.js +++ b/webpack/ForemanTasks/Components/TasksTable/TasksTableConstants.js @@ -16,7 +16,7 @@ export const CANCEL_MODAL = 'cancelConfirmModal'; export const RESUME_MODAL = 'resumeConfirmModal'; export const CANCEL_SELECTED_MODAL = 'cancelSelectedConfirmModal'; export const RESUME_SELECTED_MODAL = 'resumeSelectedConfirmModal'; -export const CONFIRM_MODAL = 'ConfirmModal'; +export const FORCE_UNLOCK_MODAL = 'forceUnlockConfirmModal'; export const FORCE_UNLOCK_SELECTED_MODAL = 'forceUnlockSelectedConfirmModal'; export const UPDATE_CLICKED = 'UPDATE_CLICKED'; diff --git a/webpack/ForemanTasks/Components/TasksTable/TasksTablePage.js b/webpack/ForemanTasks/Components/TasksTable/TasksTablePage.js index eee7b81a3..b71038bf6 100644 --- a/webpack/ForemanTasks/Components/TasksTable/TasksTablePage.js +++ b/webpack/ForemanTasks/Components/TasksTable/TasksTablePage.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import PropTypes from 'prop-types'; import URI from 'urijs'; import { getURIsearch } from 'foremanReact/common/urlHelpers'; @@ -8,17 +8,25 @@ import { translate as __ } from 'foremanReact/common/I18n'; import { getURIQuery } from 'foremanReact/common/helpers'; import ExportButton from 'foremanReact/routes/common/PageLayout/components/ExportButton/ExportButton'; import { STATUS } from 'foremanReact/constants'; -import { useForemanModal } from 'foremanReact/components/ForemanModal/ForemanModalHooks'; import TasksDashboard from '../TasksDashboard'; import TasksTable from './TasksTable'; import { getCSVurl, updateURlQuery } from './TasksTableHelpers'; -import ConfirmModal from './Components/ConfirmModal/'; +import { + CancelModal, + ResumeModal, + CancelSelectedModal, + ResumeSelectedModal, + ForceUnlockModal, + ForceUnlockSelectedModal, +} from './Components/ConfirmModal'; import { TASKS_SEARCH_PROPS, CANCEL_SELECTED_MODAL, RESUME_SELECTED_MODAL, FORCE_UNLOCK_SELECTED_MODAL, - CONFIRM_MODAL, + CANCEL_MODAL, + RESUME_MODAL, + FORCE_UNLOCK_MODAL, } from './TasksTableConstants'; import { ActionSelectButton } from './Components/ActionSelectButton'; import './TasksTablePage.scss'; @@ -48,21 +56,69 @@ const TasksTablePage = ({ updateURlQuery(newUriQuery, history); } }; - - const { setModalOpen, setModalClosed } = useForemanModal({ - id: CONFIRM_MODAL, + const [modalStates, setModalStates] = useState({ + [CANCEL_MODAL]: false, + [RESUME_MODAL]: false, + [CANCEL_SELECTED_MODAL]: false, + [RESUME_SELECTED_MODAL]: false, + [FORCE_UNLOCK_MODAL]: false, + [FORCE_UNLOCK_SELECTED_MODAL]: false, }); - const openModal = id => openModalAction(id, setModalOpen); + const openModal = id => { + setModalStates(prev => ({ + ...prev, + [id]: true, + })); + }; + + const closeModal = id => { + setModalStates(prev => ({ + ...prev, + [id]: false, + })); + }; return ( - closeModal(CANCEL_MODAL)} + url={url} + parentTaskID={props.parentTaskID} + /> + closeModal(RESUME_MODAL)} + url={url} + parentTaskID={props.parentTaskID} + /> + closeModal(CANCEL_SELECTED_MODAL)} url={url} + uriQuery={uriQuery} parentTaskID={props.parentTaskID} + /> + closeModal(RESUME_SELECTED_MODAL)} + url={url} uriQuery={uriQuery} - setModalClosed={setModalClosed} + parentTaskID={props.parentTaskID} + /> + closeModal(FORCE_UNLOCK_MODAL)} + url={url} + parentTaskID={props.parentTaskID} + /> + closeModal(FORCE_UNLOCK_SELECTED_MODAL)} + url={url} + uriQuery={uriQuery} + parentTaskID={props.parentTaskID} /> - + + + + + - + + + + + { - testComponentSnapshotsWithFixtures(ClickConfirmation, fixtures); - it('enable button on checkbox click', () => { - const component = mount(); - const getButton = () => component.find('.confirm-button').at(0); - expect(getButton().props().disabled).toBeTruthy(); - const checkbox = component.find('input').at(0); - checkbox.simulate('change', { target: { checked: true } }); - expect(getButton().props().disabled).toBeFalsy(); - }); - - it('click test', () => { - const setModalClosed = jest.fn(); - useForemanModal.mockImplementation(id => ({ - setModalClosed: () => setModalClosed(id), - })); - const onClick = jest.fn(); - const props = { ...fixtures.render, onClick }; - const component = mount(); - const getButton = () => component.find('.confirm-button').at(0); - const checkbox = component.find('input').at(0); - checkbox.simulate('change', { target: { checked: true } }); - getButton().simulate('click'); - expect(onClick).toBeCalled(); - expect(setModalClosed).toBeCalledWith({ id: fixtures.render.id }); + const defaultProps = { + title: 'Test Modal', + body: 'This is a test modal body', + confirmationMessage: 'I understand the consequences', + confirmAction: 'Confirm', + onClick: jest.fn(), + id: 'test-modal', + isOpen: true, + setModalClosed: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('Basic Rendering', () => { + it('renders modal with correct title and content when open', () => { + render(); + + expect(screen.getByText('Test Modal')).toBeInTheDocument(); + expect(screen.getByText('This is a test modal body')).toBeInTheDocument(); + expect( + screen.getByText('I understand the consequences') + ).toBeInTheDocument(); + expect(screen.getByText('Confirm')).toBeInTheDocument(); + }); + + it('does not render modal when isOpen is false', () => { + render(); + + expect(screen.queryByText('Test Modal')).not.toBeInTheDocument(); + }); + + it('renders with warning variant by default', () => { + render(); + + const modal = screen.getByRole('dialog'); + expect(modal).toBeInTheDocument(); + }); + + it('renders with danger variant when specified', () => { + render(); + + const confirmButton = screen.getByRole('button', { name: 'Confirm' }); + expect(confirmButton).toHaveClass('pf-m-danger'); + }); + }); + + describe('Checkbox Interaction', () => { + it('starts with checkbox unchecked and confirm button disabled', () => { + render(); + + const checkbox = screen.getByRole('checkbox'); + const confirmButton = screen.getByRole('button', { name: 'Confirm' }); + + expect(checkbox).not.toBeChecked(); + expect(confirmButton).toBeDisabled(); + }); + + it('enables confirm button when checkbox is checked', () => { + render(); + + const checkbox = screen.getByRole('checkbox'); + const confirmButton = screen.getByRole('button', { name: 'Confirm' }); + + fireEvent.click(checkbox); + + expect(checkbox).toBeChecked(); + expect(confirmButton).toBeEnabled(); + }); + + it('disables confirm button when checkbox is unchecked', () => { + render(); + + const checkbox = screen.getByRole('checkbox'); + const confirmButton = screen.getByRole('button', { name: 'Confirm' }); + + // Check the checkbox first + fireEvent.click(checkbox); + expect(confirmButton).toBeEnabled(); + + // Uncheck the checkbox + fireEvent.click(checkbox); + expect(checkbox).not.toBeChecked(); + expect(confirmButton).toBeDisabled(); + }); + }); + + describe('Button Actions', () => { + it('calls onClick and setModalClosed when confirm button is clicked', () => { + const onClick = jest.fn(); + const setModalClosed = jest.fn(); + + render( + + ); + + const checkbox = screen.getByRole('checkbox'); + const confirmButton = screen.getByRole('button', { name: 'Confirm' }); + + // Enable the button by checking the checkbox + fireEvent.click(checkbox); + + // Click the confirm button + fireEvent.click(confirmButton); + + expect(onClick).toHaveBeenCalledTimes(1); + expect(setModalClosed).toHaveBeenCalledTimes(1); + }); + + it('calls setModalClosed when cancel button is clicked', () => { + const setModalClosed = jest.fn(); + + render( + + ); + + const cancelButton = screen.getByRole('button', { name: 'Cancel' }); + fireEvent.click(cancelButton); + + expect(setModalClosed).toHaveBeenCalledTimes(1); + }); + + it('does not call onClick when confirm button is clicked while disabled', () => { + const onClick = jest.fn(); + + render(); + + const confirmButton = screen.getByRole('button', { name: 'Confirm' }); + + // Try to click the disabled button + fireEvent.click(confirmButton); + + expect(onClick).not.toHaveBeenCalled(); + }); + }); +}); + +describe('UnlockModal', () => { + const defaultProps = { + onClick: jest.fn(), + id: 'unlock-modal', + isOpen: true, + setModalClosed: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders with correct unlock-specific content and warning variant', () => { + render(); + + expect( + screen.getByRole('heading', { name: 'Warning alert: Unlock' }) + ).toBeInTheDocument(); + expect( + screen.getByText( + /This will unlock the resources that the task is running against/ + ) + ).toBeInTheDocument(); + expect( + screen.getByText( + 'I understand that this may cause harm and have working database backups of all backend services.' + ) + ).toBeInTheDocument(); + + const confirmButton = screen.getByRole('button', { name: 'Unlock' }); + expect(confirmButton).toBeInTheDocument(); + expect(confirmButton).toHaveClass('pf-m-primary'); + }); +}); + +describe('ForceUnlockModal', () => { + const defaultProps = { + onClick: jest.fn(), + id: 'force-unlock-modal', + selectedRowsLen: 3, + isOpen: true, + setModalClosed: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders with correct force unlock-specific content and danger variant', () => { + render(); + + expect( + screen.getByRole('heading', { name: 'Danger alert: Force Unlock' }) + ).toBeInTheDocument(); + expect( + screen.getByText(/Resources for 3 task\(s\) will be unlocked/) + ).toBeInTheDocument(); + expect( + screen.getByText( + 'I understand that this may cause harm and have working database backups of all backend services.' + ) + ).toBeInTheDocument(); + + const confirmButton = screen.getByRole('button', { name: 'Force Unlock' }); + expect(confirmButton).toBeInTheDocument(); + expect(confirmButton).toHaveClass('pf-m-danger'); + }); + + it('displays correct number of tasks in body', () => { + render( + + ); + + expect( + screen.getByText(/Resources for 5 task\(s\) will be unlocked/) + ).toBeInTheDocument(); }); }); diff --git a/webpack/ForemanTasks/Components/common/ClickConfirmation/__snapshots__/ClickConfirmation.test.js.snap b/webpack/ForemanTasks/Components/common/ClickConfirmation/__snapshots__/ClickConfirmation.test.js.snap deleted file mode 100644 index b53efce7f..000000000 --- a/webpack/ForemanTasks/Components/common/ClickConfirmation/__snapshots__/ClickConfirmation.test.js.snap +++ /dev/null @@ -1,52 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ClickConfirmation render 1`] = ` - - - - some-title - - - some-body - - - - - some-message - - - - - some-confirm - - - Cancel - - - -`; diff --git a/webpack/ForemanTasks/Components/common/ClickConfirmation/index.js b/webpack/ForemanTasks/Components/common/ClickConfirmation/index.js index 392b3129a..0b5898738 100644 --- a/webpack/ForemanTasks/Components/common/ClickConfirmation/index.js +++ b/webpack/ForemanTasks/Components/common/ClickConfirmation/index.js @@ -1,10 +1,17 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState } from 'react'; import PropTypes from 'prop-types'; -import { Button } from 'patternfly-react'; -import ForemanModal from 'foremanReact/components/ForemanModal'; -import { translate as __ } from 'foremanReact/common/I18n'; -import { useForemanModal } from 'foremanReact/components/ForemanModal/ForemanModalHooks'; -import './ClickConfirmation.scss'; +import { + Button, + Modal, + ModalVariant, + Checkbox, + TextContent, +} from '@patternfly/react-core'; +import { translate as __, sprintf } from 'foremanReact/common/I18n'; +import { + UNLOCK_MODAL, + FORCE_UNLOCK_MODAL, +} from '../../TaskActions/TaskActionsConstants'; export const ClickConfirmation = ({ title, @@ -14,48 +21,57 @@ export const ClickConfirmation = ({ id, confirmAction, onClick, + isOpen, + setModalClosed, }) => { const [isConfirmed, setConfirm] = useState(false); - const { setModalClosed, modalOpen } = useForemanModal({ - id, - }); - useEffect(() => { + const onClose = () => { setConfirm(false); - }, [modalOpen]); - const icon = confirmType === 'warning' ? confirmType : 'exclamation'; - + setModalClosed(); + }; return ( - - - - {` ${title}`} - - {body} - - { - setConfirm(e.target.checked); - }} - checked={isConfirmed} - type="checkbox" - /> - {` ${confirmationMessage}`} - - + + {__('Cancel')} + , + { onClick(); - setModalClosed(); + onClose(); }} - bsStyle={confirmType} - disabled={!isConfirmed} + isDisabled={!isConfirmed} > {confirmAction} - - {__('Cancel')} - - + , + ]} + > + + {body} + setConfirm(checked)} + /> + + ); }; @@ -67,10 +83,93 @@ ClickConfirmation.propTypes = { onClick: PropTypes.func.isRequired, confirmType: PropTypes.oneOf(['warning', 'danger']), id: PropTypes.string.isRequired, + isOpen: PropTypes.bool, + setModalClosed: PropTypes.func, }; ClickConfirmation.defaultProps = { confirmType: 'warning', + isOpen: false, + setModalClosed: () => {}, +}; + +// Unlock Modal Components +const confirmationMessage = __( + 'I understand that this may cause harm and have working database backups of all backend services.' +); + +export const UnlockConfirmationModal = ({ + onClick, + id, + isOpen, + setModalClosed, +}) => ( + +); + +export const ForceUnlockConfirmationModal = ({ + onClick, + id, + selectedRowsLen, + isOpen, + setModalClosed, +}) => ( + +); + +UnlockConfirmationModal.propTypes = { + onClick: PropTypes.func.isRequired, + id: PropTypes.string, + isOpen: PropTypes.bool, + setModalClosed: PropTypes.func, +}; + +ForceUnlockConfirmationModal.propTypes = { + onClick: PropTypes.func.isRequired, + id: PropTypes.string, + selectedRowsLen: PropTypes.number, + isOpen: PropTypes.bool, + setModalClosed: PropTypes.func, +}; + +UnlockConfirmationModal.defaultProps = { + id: UNLOCK_MODAL, + isOpen: false, + setModalClosed: () => {}, +}; + +ForceUnlockConfirmationModal.defaultProps = { + id: FORCE_UNLOCK_MODAL, + selectedRowsLen: 1, + isOpen: false, + setModalClosed: () => {}, }; export default ClickConfirmation; diff --git a/webpack/__mocks__/foremanReact/components/ForemanModal/ForemanModalActions.js b/webpack/__mocks__/foremanReact/components/ForemanModal/ForemanModalActions.js deleted file mode 100644 index 991198e51..000000000 --- a/webpack/__mocks__/foremanReact/components/ForemanModal/ForemanModalActions.js +++ /dev/null @@ -1,2 +0,0 @@ -const ForemanModalActions = () => jest.fn(); -export default ForemanModalActions; diff --git a/webpack/__mocks__/foremanReact/components/ForemanModal/ForemanModalHooks.js b/webpack/__mocks__/foremanReact/components/ForemanModal/ForemanModalHooks.js deleted file mode 100644 index d6dba51ed..000000000 --- a/webpack/__mocks__/foremanReact/components/ForemanModal/ForemanModalHooks.js +++ /dev/null @@ -1,10 +0,0 @@ -const modalOpen = true; -const setModalOpen = jest.fn(); -const setModalClosed = jest.fn(); - -export const useForemanModal = jest.fn(() => ({ - modalOpen, - setModalOpen, - setModalClosed, -})); -export default useForemanModal; diff --git a/webpack/__mocks__/foremanReact/components/ForemanModal/index.js b/webpack/__mocks__/foremanReact/components/ForemanModal/index.js deleted file mode 100644 index c662e8b2a..000000000 --- a/webpack/__mocks__/foremanReact/components/ForemanModal/index.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -const ForemanModal = ({ children }) => {children}; -ForemanModal.Header = ({ children }) => ( - {children} -); -ForemanModal.Footer = ({ children }) => ( - {children} -); - -ForemanModal.propTypes = { - children: PropTypes.node.isRequired, -}; -ForemanModal.Header.propTypes = ForemanModal.propTypes; -ForemanModal.Footer.propTypes = ForemanModal.propTypes; - -export default ForemanModal;