diff --git a/packages/ui/src/providers/__snapshots__/action-confirmaton-modal.provider.test.tsx.snap b/packages/ui/src/providers/__snapshots__/action-confirmaton-modal.provider.test.tsx.snap index 5144bfe4e..06009139c 100644 --- a/packages/ui/src/providers/__snapshots__/action-confirmaton-modal.provider.test.tsx.snap +++ b/packages/ui/src/providers/__snapshots__/action-confirmaton-modal.provider.test.tsx.snap @@ -90,6 +90,7 @@ exports[`ActionConfirmationModalProvider should allow consumers to update the mo data-ouia-component-id="OUIA-Generated-Button-danger-3" data-ouia-component-type="PF5/Button" data-ouia-safe="true" + data-testid="action-confirmation-modal-btn-1" type="button" > Confirm @@ -100,6 +101,7 @@ exports[`ActionConfirmationModalProvider should allow consumers to update the mo data-ouia-component-id="OUIA-Generated-Button-link-3" data-ouia-component-type="PF5/Button" data-ouia-safe="true" + data-testid="action-confirmation-modal-btn-0" type="button" > Cancel diff --git a/packages/ui/src/providers/action-confirmation-modal.provider.tsx b/packages/ui/src/providers/action-confirmation-modal.provider.tsx index 9445fb870..598a2b7f6 100644 --- a/packages/ui/src/providers/action-confirmation-modal.provider.tsx +++ b/packages/ui/src/providers/action-confirmation-modal.provider.tsx @@ -1,8 +1,22 @@ -import { Button, Modal, ModalVariant } from '@patternfly/react-core'; +import { Button, ButtonVariant, Modal, ModalVariant } from '@patternfly/react-core'; import { FunctionComponent, PropsWithChildren, createContext, useCallback, useMemo, useRef, useState } from 'react'; +export const ACTION_INDEX_CANCEL = 0; +export const ACTION_INDEX_CONFIRM = 1; +export interface ActionConfirmationButtonOption { + index: number; + buttonText: string; + variant: ButtonVariant; + isDanger?: boolean; +} + interface ActionConfirmationModalContextValue { - actionConfirmation: (options: { title?: string; text?: string }) => Promise; + actionConfirmation: (options: { + title?: string; + text?: string; + buttonOptions?: ActionConfirmationButtonOption[]; + additionalModalText?: string; + }) => Promise; } export const ActionConfirmationModalContext = createContext(undefined); @@ -14,35 +28,52 @@ export const ActionConfirmationModalContext = createContext = (props) => { const [isModalOpen, setIsModalOpen] = useState(false); const [title, setTitle] = useState(''); - const [text, setText] = useState(''); - + const [textParagraphs, setTextParagraphs] = useState([]); + const [buttonOptions, setButtonOptions] = useState([]); const actionConfirmationRef = useRef<{ - resolve: (confirm: boolean) => void; + resolve: (index: number) => void; reject: (error: unknown) => unknown; }>(); const handleCloseModal = useCallback(() => { setIsModalOpen(false); - actionConfirmationRef.current?.resolve(false); + actionConfirmationRef.current?.resolve(ACTION_INDEX_CANCEL); }, []); - const handleActionConfirm = useCallback(() => { + const handleAction = useCallback((index: number) => { setIsModalOpen(false); - actionConfirmationRef.current?.resolve(true); + actionConfirmationRef.current?.resolve(index); }, []); - const actionConfirmation = useCallback((options: { title?: string; text?: string } = {}) => { - const actionConfirmationPromise = new Promise((resolve, reject) => { - /** Set both resolve and reject functions to be used once the user choose an action */ - actionConfirmationRef.current = { resolve, reject }; - }); + const actionConfirmation = useCallback( + ( + options: { + title?: string; + text?: string; + additionalModalText?: string; + buttonOptions?: ActionConfirmationButtonOption[]; + } = {}, + ) => { + const actionConfirmationPromise = new Promise((resolve, reject) => { + /** Set both resolve and reject functions to be used once the user choose an action */ + actionConfirmationRef.current = { resolve, reject }; + }); - setTitle(options.title ?? 'Delete?'); - setText(options.text ?? 'Are you sure you want to delete?'); - setIsModalOpen(true); + setTitle(options.title ?? 'Delete?'); + const textParagraphs = [options.text ?? 'Are you sure you want to delete?']; + if (options.additionalModalText) { + textParagraphs.push(options.additionalModalText); + } + setTextParagraphs(textParagraphs); + options.buttonOptions + ? setButtonOptions(options.buttonOptions) + : setButtonOptions([{ index: ACTION_INDEX_CONFIRM, buttonText: 'Confirm', variant: ButtonVariant.danger }]); + setIsModalOpen(true); - return actionConfirmationPromise; - }, []); + return actionConfirmationPromise; + }, + [], + ); const value: ActionConfirmationModalContextValue = useMemo( () => ({ @@ -64,15 +95,30 @@ export const ActionConfirmationModalContextProvider: FunctionComponent - Confirm - , - + )), + , ]} > - {text} + {textParagraphs.length === 1 + ? textParagraphs[0] + : textParagraphs.map((paragraph, index) =>

{paragraph}

)} )} diff --git a/packages/ui/src/providers/action-confirmaton-modal.provider.test.tsx b/packages/ui/src/providers/action-confirmaton-modal.provider.test.tsx index 2f188fb47..b2b4ed642 100644 --- a/packages/ui/src/providers/action-confirmaton-modal.provider.test.tsx +++ b/packages/ui/src/providers/action-confirmaton-modal.provider.test.tsx @@ -1,11 +1,13 @@ import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'; import { FunctionComponent, useContext } from 'react'; import { + ActionConfirmationButtonOption, ActionConfirmationModalContext, ActionConfirmationModalContextProvider, } from './action-confirmation-modal.provider'; +import { ButtonVariant } from '@patternfly/react-core'; -let actionConfirmationResult: boolean | undefined; +let actionConfirmationResult: number | undefined; describe('ActionConfirmationModalProvider', () => { beforeEach(() => { @@ -26,7 +28,7 @@ describe('ActionConfirmationModalProvider', () => { fireEvent.click(confirmButton); // Wait for actionConfirmation promise to resolve - await waitFor(() => expect(actionConfirmationResult).toEqual(true)); + await waitFor(() => expect(actionConfirmationResult).toEqual(1)); }); it('calls actionConfirmation with false when Cancel button is clicked', async () => { @@ -43,7 +45,7 @@ describe('ActionConfirmationModalProvider', () => { fireEvent.click(cancelButton); // Wait for actionConfirmation promise to resolve - await waitFor(() => expect(actionConfirmationResult).toEqual(false)); + await waitFor(() => expect(actionConfirmationResult).toEqual(0)); }); it('should allow consumers to update the modal title and text', () => { @@ -64,11 +66,79 @@ describe('ActionConfirmationModalProvider', () => { expect(wrapper.queryByText('Custom title')).toBeInTheDocument; expect(wrapper.queryByText('Custom text')).toBeInTheDocument; }); + + it('should show 3 options to choose', async () => { + const wrapper = render( + + + , + ); + + act(() => { + const deleteButton = wrapper.getByText('Delete'); + fireEvent.click(deleteButton); + }); + const modalDialog = wrapper.getByRole('dialog'); + expect(modalDialog.textContent).toContain('Additional text is added in the modal description'); + act(() => { + const cancelButton = wrapper.getByTestId('action-confirmation-modal-btn-0'); + expect(cancelButton.textContent).toEqual('Cancel'); + fireEvent.click(cancelButton); + }); + await waitFor(() => { + expect(actionConfirmationResult).toEqual(0); + }); + + act(() => { + const deleteButton = wrapper.getByText('Delete'); + fireEvent.click(deleteButton); + }); + act(() => { + const deleteStepAndFileButton = wrapper.getByTestId('action-confirmation-modal-btn-1'); + expect(deleteStepAndFileButton.textContent).toEqual('Delete the step, and delete the file(s)'); + fireEvent.click(deleteStepAndFileButton); + }); + await waitFor(() => { + expect(actionConfirmationResult).toEqual(1); + }); + + act(() => { + const deleteButton = wrapper.getByText('Delete'); + fireEvent.click(deleteButton); + }); + act(() => { + const deleteStepOnlyButton = wrapper.getByTestId('action-confirmation-modal-btn-2'); + expect(deleteStepOnlyButton.textContent).toEqual('Delete the step, but keep the file(s)'); + fireEvent.click(deleteStepOnlyButton); + }); + await waitFor(() => { + expect(actionConfirmationResult).toEqual(2); + }); + }); }); interface TestComponentProps { title: string; text: string; + additionalModalText?: string; + buttonOptions?: ActionConfirmationButtonOption[]; } const TestComponent: FunctionComponent = (props) => {