From e44bc147b707b30a03b468db555e37d4df978e3b Mon Sep 17 00:00:00 2001 From: Branden Brown <29528409+20BBrown14@users.noreply.github.com> Date: Tue, 1 Feb 2022 15:44:10 -0600 Subject: [PATCH] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20Migrate=20SQFormGuid?= =?UTF-8?q?edWorkflow=20to=20TS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BREAKING CHANGE: ๐Ÿงจ SQFormGuidedWorkflow prop 'actionButton' renamed to 'actions'. Removed props 'isPanelExpanded' and 'expandPanel' โœ… Closes: #567 --- README.md | 38 ++++ ...on.js => AdditionalInformationSection.tsx} | 10 +- .../{AgentScript.js => AgentScript.tsx} | 10 +- .../{Header.js => Header.tsx} | 12 +- .../{OutcomeForm.js => OutcomeForm.tsx} | 10 +- .../SQFormGuidedWorkflow/PropTypes.js | 126 ------------- src/components/SQFormGuidedWorkflow/README.md | 3 + ...edWorkflow.js => SQFormGuidedWorkflow.tsx} | 57 +++--- src/components/SQFormGuidedWorkflow/Types.ts | 165 ++++++++++++++++++ .../{index.js => index.ts} | 0 .../useGuidedWorkflowContext.js | 44 ----- .../useGuidedWorkflowContext.ts | 61 +++++++ ...TaskModules.js => useManageTaskModules.ts} | 55 ++++-- stories/SQFormGuidedWorkflow.stories.js | 2 +- .../SQFormGuidedWorkflow.stories.test.js | 18 +- 15 files changed, 374 insertions(+), 237 deletions(-) rename src/components/SQFormGuidedWorkflow/{AdditionalInformationSection.js => AdditionalInformationSection.tsx} (75%) rename src/components/SQFormGuidedWorkflow/{AgentScript.js => AgentScript.tsx} (79%) rename src/components/SQFormGuidedWorkflow/{Header.js => Header.tsx} (89%) rename src/components/SQFormGuidedWorkflow/{OutcomeForm.js => OutcomeForm.tsx} (83%) delete mode 100644 src/components/SQFormGuidedWorkflow/PropTypes.js rename src/components/SQFormGuidedWorkflow/{SQFormGuidedWorkflow.js => SQFormGuidedWorkflow.tsx} (78%) create mode 100644 src/components/SQFormGuidedWorkflow/Types.ts rename src/components/SQFormGuidedWorkflow/{index.js => index.ts} (100%) delete mode 100644 src/components/SQFormGuidedWorkflow/useGuidedWorkflowContext.js create mode 100644 src/components/SQFormGuidedWorkflow/useGuidedWorkflowContext.ts rename src/components/SQFormGuidedWorkflow/{useManageTaskModules.js => useManageTaskModules.ts} (72%) diff --git a/README.md b/README.md index ae65d427..96c99f13 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,44 @@ For **BREAKING CHANGES** Type a brief description of the breaking change when as ## Breaking Changes ### Version `[Typescript Version]` + +#### SQFormGuidedWorkflow changes + - Removed TaskModule properties: `isPanelExpanded` and `expandPanel` + + + - In SQForm v`[Typescript Version]` `actionButton` was renamed to `actions` as part of the taskModule definitions. Functionality remains the same. + +```jsx +// โ›”๏ธ Example +const taskModules = [ + { ... }, + { ... }, + { + ... + scriptedTextProps: { + text: `Stuff about policy cancellation documents`, + title: 'Agent Script', + actionButton: View Doc, // This prop was renamed + }, + } +] + +// โœ… Example +const taskModules = [ + { ... }, + { ... }, + { + ... + scriptedTextProps: { + text: `Stuff about policy cancellation documents`, + title: 'Agent Script', + actions: View Doc, // This prop was renamed + }, + } +] +``` + +#### SQForm no longer allows `boolean`s as dropdown options In SQForm v`[Typescript Version]` support for `boolean` valued dropdown options was removed. Material-UI and HTML Select components do not support options with `boolean`s as values and causes type conflicts with our own library. Therefore, if you're upgrading this version and you using `boolean` values options you'll need to take care to update those. Below is our recommended changes. ```js diff --git a/src/components/SQFormGuidedWorkflow/AdditionalInformationSection.js b/src/components/SQFormGuidedWorkflow/AdditionalInformationSection.tsx similarity index 75% rename from src/components/SQFormGuidedWorkflow/AdditionalInformationSection.js rename to src/components/SQFormGuidedWorkflow/AdditionalInformationSection.tsx index 47bb7ec5..32d374a8 100644 --- a/src/components/SQFormGuidedWorkflow/AdditionalInformationSection.js +++ b/src/components/SQFormGuidedWorkflow/AdditionalInformationSection.tsx @@ -1,10 +1,10 @@ import React from 'react'; import {Section, SectionBody} from 'scplus-shared-components'; -import {AdditionalInformationPropTypes} from './PropTypes'; import Header from './Header'; +import type {AdditionalInformationProps} from './Types'; function AdditionalInformationSection({ - actionButton, + actions, title, infoText, warningText, @@ -12,11 +12,11 @@ function AdditionalInformationSection({ successText, isFailedState, Elements, -}) { +}: AdditionalInformationProps): React.ReactElement { return (
{ - if (!shouldRenderInformativeHeading) { + if (!shouldRenderInformativeHeading || !informativeHeadingType) { return; } @@ -66,11 +66,9 @@ function Header({ informativeHeading={getInformativeHeadingText()} type={informativeHeadingType} > - {actionButton} + {actions} ); } -Header.propTypes = HeaderPropTypes; - export default Header; diff --git a/src/components/SQFormGuidedWorkflow/OutcomeForm.js b/src/components/SQFormGuidedWorkflow/OutcomeForm.tsx similarity index 83% rename from src/components/SQFormGuidedWorkflow/OutcomeForm.js rename to src/components/SQFormGuidedWorkflow/OutcomeForm.tsx index 30557916..347bae09 100644 --- a/src/components/SQFormGuidedWorkflow/OutcomeForm.js +++ b/src/components/SQFormGuidedWorkflow/OutcomeForm.tsx @@ -2,10 +2,10 @@ import React from 'react'; import {Grid} from '@material-ui/core'; import {Section, SectionBody} from 'scplus-shared-components'; import Header from './Header'; -import {OutcomePropTypes} from './PropTypes'; +import type {OutcomeProps} from './Types'; function OutcomeForm({ - actionButton, + actions, FormElements, title, infoText, @@ -14,11 +14,11 @@ function OutcomeForm({ successText, isFailedState, muiGridProps = {}, -}) { +}: OutcomeProps): React.ReactElement { return (
component */ - muiGridProps: PropTypes.object, - ...HeaderPropTypes, -}; - -export const FormikProps = { - /** Form Entity Object aka initial values of the form */ - initialValues: PropTypes.object.isRequired, - /** Form Submission Handler | @typedef onSubmit: (values: Values, formikBag: FormikBag, context) => void | Promise */ - onSubmit: PropTypes.func.isRequired, - /** Yup validation schema shape */ - validationSchema: PropTypes.object, -}; - -export const TaskModuleProps = { - /** Unique name used as a key for managing expansion state within Accordion */ - name: PropTypes.string.isRequired, - /** Title text */ - title: PropTypes.string.isRequired, - /** Subtitle text - Each Subtitle is separated by a pipe "|" */ - subtitles: PropTypes.arrayOf(PropTypes.string), - /** Panel is disabled, the user cannot toggle the panel while disabled */ - isDisabled: PropTypes.bool, - /** Is initially expanded */ - isInitiallyExpanded: PropTypes.bool, - /** Controlled loading */ - isLoading: PropTypes.bool, - /** Custom loading message for controlled loading */ - isLoadingMessage: PropTypes.string, - /** expandPanel callback synchronizes consumer state with Accordion state. - * Requires isPanelExpanded prop. */ - expandPanel: PropTypes.func, - /** Optional prop for the consumer to define the cards open/close state. - * Requires expandPanel prop. */ - isPanelExpanded: PropTypes.bool, - /** Optional click handler if you want to perform a side effect on click */ - onClick: PropTypes.func, - /** Reset button text */ - resetButtonText: PropTypes.string, - /** Submit button text */ - submitButtonText: PropTypes.string, - /** Flag for if the form is in a failed state where the user cannot continue */ - isFailedState: PropTypes.bool, - /** Manually disable submit button */ - isSubmitButtonDisabled: PropTypes.bool, - /** The props used to configure SQForm */ - formikProps: PropTypes.shape(FormikProps).isRequired, - /** The props used to configure the Additional Information section */ - additionalInformationSectionProps: PropTypes.shape( - AdditionalInformationPropTypes - ), - /** The props used to configure the Scripted Text section */ - scriptedTextProps: PropTypes.shape(AgentScriptPropTypes).isRequired, - /** The props used to configure the Outcome form section */ - outcomeProps: PropTypes.shape(OutcomePropTypes).isRequired, -}; - -export const GuidedWorkflowProps = { - /** Main Title */ - mainTitle: PropTypes.string.isRequired, - /** Main Subtitle Informative Text */ - mainSubtitle: PropTypes.string, - /** Number of tasks completed (Default is zero) */ - initialCompletedTasks: PropTypes.number, - /** - * Disables all Task Modules except the current Active Task module - * This prevents the user from returning to a previous task module - */ - isStrictMode: PropTypes.bool, - /** - * Callback function which passes the error as an argument for the consumer to handle - * Usually the consumer will render an alert to signal an error occured - */ - onError: PropTypes.func.isRequired, - /** Task Module configuration Object(s) */ - taskModules: PropTypes.arrayOf(PropTypes.shape(TaskModuleProps).isRequired) - .isRequired, - /** An object of css-in-js style properties to be passed */ - containerStyles: PropTypes.object, -}; diff --git a/src/components/SQFormGuidedWorkflow/README.md b/src/components/SQFormGuidedWorkflow/README.md index 3d2e4718..c7e1f79d 100644 --- a/src/components/SQFormGuidedWorkflow/README.md +++ b/src/components/SQFormGuidedWorkflow/README.md @@ -14,6 +14,9 @@ This section covers how to properly setup the configuration object array for the - [The Task Module Props](https://www.loom.com/share/ca6de9ff9cfc4faba4d500e8c244c5d9) > After recording this video I made a code change that added another prop to the task modules props, `isFailedState` -[The `isFailedState` Task Module Prop](https://www.loom.com/share/5a636e9ca7884bcf9697b00ba7f09da2) + > Changes were made that removed the following props: `isPanelExpanded` and `expandPanel`. See the Breaking Changes section of the project README for more information. + > The `actionButton` prop was renamed to `actions`. Functionality remains the same. See the Breaking Changes section of the project README for more information. + ### Scripted Text Section diff --git a/src/components/SQFormGuidedWorkflow/SQFormGuidedWorkflow.js b/src/components/SQFormGuidedWorkflow/SQFormGuidedWorkflow.tsx similarity index 78% rename from src/components/SQFormGuidedWorkflow/SQFormGuidedWorkflow.js rename to src/components/SQFormGuidedWorkflow/SQFormGuidedWorkflow.tsx index 92b43d67..a7a8909a 100644 --- a/src/components/SQFormGuidedWorkflow/SQFormGuidedWorkflow.js +++ b/src/components/SQFormGuidedWorkflow/SQFormGuidedWorkflow.tsx @@ -1,6 +1,7 @@ import React from 'react'; import * as Yup from 'yup'; import {Formik, Form} from 'formik'; +import type {FormikHelpers} from 'formik'; import {CardActions, CardContent, makeStyles} from '@material-ui/core'; import { Accordion, @@ -15,7 +16,7 @@ import OutcomeForm from './OutcomeForm'; import AdditionalInformationSection from './AdditionalInformationSection'; import {useManageTaskModules} from './useManageTaskModules'; import {useGuidedWorkflowContext} from './useGuidedWorkflowContext'; -import {GuidedWorkflowProps} from './PropTypes'; +import type {SQFormDataProps, SQFormGuidedWorkflowProps} from './Types'; const useStyles = makeStyles(() => { return { @@ -35,18 +36,7 @@ const getTaskModuleFormSchema = (validationSchema = {}) => { return Yup.object().shape(validationSchema); }; -// Until Formik exposes the validationSchema (again) via Context, the solution has to be handled at the Form declaration level -// There's a few open PR's on this issue, here's one for reference: https://github.com/formium/formik/pull/2933 -const getFormikInitialRequiredErrors = (validationSchema = {}) => { - Object.entries(validationSchema).reduce((acc, [key, value]) => { - if (value._exclusive?.required) { - return {...acc, [key]: 'Required'}; - } - return acc; - }, {}); -}; - -function SQFormGuidedWorkflow({ +function SQFormGuidedWorkflow({ taskModules, mainTitle, mainSubtitle, @@ -54,14 +44,33 @@ function SQFormGuidedWorkflow({ isStrictMode = false, onError, containerStyles = {}, -}) { +}: SQFormGuidedWorkflowProps): React.ReactElement { + // Until Formik exposes the validationSchema (again) via Context, the solution has to be handled at the Form declaration level + // There's a few open PR's on this issue, here's one for reference: https://github.com/formium/formik/pull/2933 + const getFormikInitialRequiredErrors = ( + validationSchema?: SQFormDataProps['validationSchema'] + ) => { + if (validationSchema) { + return Object.entries(validationSchema).reduce((acc, [key, value]) => { + if (value.tests[0]?.OPTIONS.name === 'required') { + return {...acc, [key]: 'Required'}; + } + return acc; + }, {}); + } + + return {}; + }; + const classes = useStyles(); - const [taskModulesContext, updateTaskModuleContextByID] = - useGuidedWorkflowContext(taskModules); + const { + state: taskModulesContext, + updateDataByID: updateTaskModuleContextByID, + } = useGuidedWorkflowContext(taskModules); const {taskModulesState, updateActiveTaskModule, enableNextTaskModule} = - useManageTaskModules(initialCompletedTasks, taskModulesContext); + useManageTaskModules(initialCompletedTasks, taskModulesContext); const transformedTaskModules = taskModules.map((taskModule, index) => { const taskNumber = index + 1; @@ -88,7 +97,10 @@ function SQFormGuidedWorkflow({ return false; }; - const handleSubmit = async (values, formikBag) => { + const handleSubmit = async ( + values: TValues, + formikBag: FormikHelpers + ) => { const context = { ...taskModulesContext, [taskNumber]: { @@ -102,7 +114,7 @@ function SQFormGuidedWorkflow({ updateTaskModuleContextByID(taskNumber, values); enableNextTaskModule(); } catch (error) { - onError(error); + onError && onError(error); } }; @@ -110,9 +122,12 @@ function SQFormGuidedWorkflow({ ...taskModule, isDisabled: getIsDisabled(), isInitiallyExpanded: taskModule.isInitiallyExpanded || isPanelExpanded, - expandPanel: () => {}, // Faulty logic in the Accordion SSC requires precense of a function for isPanelExpanded to work + expandPanel: () => { + /* do nothing */ + }, // Faulty logic in the Accordion SSC requires precense of a function for isPanelExpanded to work isPanelExpanded, onClick: () => { + taskModule?.onClick && taskModule.onClick(); updateActiveTaskModule(taskNumber); }, body: ( @@ -188,6 +203,4 @@ function SQFormGuidedWorkflow({ ); } -SQFormGuidedWorkflow.propTypes = GuidedWorkflowProps; - export default SQFormGuidedWorkflow; diff --git a/src/components/SQFormGuidedWorkflow/Types.ts b/src/components/SQFormGuidedWorkflow/Types.ts new file mode 100644 index 00000000..50ec9c52 --- /dev/null +++ b/src/components/SQFormGuidedWorkflow/Types.ts @@ -0,0 +1,165 @@ +import React from 'react'; +import type {GridProps} from '@material-ui/core'; +import type {FormikHelpers} from 'formik'; +import type {AnySchema} from 'yup'; + +export interface HeaderProps { + /** Title to display in the section header */ + title: string; + /** Optional elements to display in the section header */ + actions?: React.ReactElement; + /** Informative text to display as a subheader next to the title */ + infoText?: string; + /** Warning text to display as a subheader next to the title */ + warningText?: string; + /** Error text to display as a subheader next to the title + * if the form is in a failed state + */ + errorText?: string; + /** Success text to display as a subheader next to the title + * if the form has no errors and is in a Valid state + */ + successText?: string; + /** This prop is controlled by taskModules.isFailedState + * Determines if the form is in a failed state where the user cannot continue + */ + isFailedState?: boolean; +} + +export interface AdditionalInformationProps extends HeaderProps { + Elements: React.ReactElement; +} + +export interface AgentScriptProps extends HeaderProps { + /** Scripted Text for the user to read */ + text: string; +} + +export interface OutcomeProps extends HeaderProps { + /** SQForm Elements to render inside the Form */ + FormElements: React.ReactElement; + /** Any props from MUI component */ + muiGridProps?: GridProps; +} + +export interface SQFormDataProps { + /** Form Entity Object aka initial values of the form */ + initialValues: TValues; + /** Form Submission Handler | @typedef onSubmit: (values: Values, formikBag: FormikBag, context) => void | Promise */ + onSubmit: ( + values: TValues, + formikBag: FormikHelpers, + context: Context + ) => void | Promise; + /** Yup validation schema shape */ + validationSchema: Record< + keyof TValues, + AnySchema + >; +} + +export interface TaskModuleProps { + /** Unique name used as a key for managing expansion state within Accordion */ + name: string; + /** Title text */ + title: string; + /** The props used to configure SQForm */ + formikProps: SQFormDataProps; + /** The props used to configured the Additional Information section */ + additionalInformationSectionProps: AdditionalInformationProps; + /** The props used to configure the Scripted Text section */ + scriptedTextProps: AgentScriptProps; + /** The props used to configure the Outcome form section */ + outcomeProps: OutcomeProps; + /** Subtitle text - Each Subtitle is separated by a pipe "|" */ + subtitles?: Array; + /** Panel is disabled, the user cannot toggle the panel while disabled */ + isDisabled?: boolean; + /** Whether the panel is initially expanded */ + isInitiallyExpanded?: boolean; + /** Whether the module is loading */ + isLoading?: boolean; + /** Custom loading message to display when module is loading */ + isLoadingMessage?: string; + /** Optional click handler if you want to perform a side effect on click */ + onClick?: () => void; + /** Reset button text */ + resetButtonText?: string; + /** Submit button text */ + submitButtonText?: string; + /** Flag for if the form is in a failed state where the user cannot continue */ + isFailedState?: boolean; + /** Whether the submit button is disabled */ + isSubmitButtonDisabled?: boolean; +} + +export interface SQFormGuidedWorkflowProps { + /** Main Title */ + mainTitle: string; + /** Main Subtitle Informative Text */ + mainSubtitle: string; + /** Task Module configuration Object(s) */ + taskModules: Array>; + /** Number of tasks completed (Default is zero) */ + initialCompletedTasks?: number; + /** + * Disables all Task Modules except the current Active Task module + * This prevents the user from returning to a previous task module + */ + isStrictMode?: boolean; + /** + * Callback function which passes the error as an argument for the consumer to handle + * Usually the consumer will render an alert to signal an error occured + */ + onError?: (error: Error) => void; + /** An object of css-in-js style properties to be passed */ + containerStyles?: React.CSSProperties; +} + +export type GuidedWorkflowDispatchActionType = { + type: 'UPDATE'; + id: number; + data: TValues; +}; + +export type ManageTaskModulesUpdateActionType = { + type: 'UPDATE_ACTIVE_TASK_MODULE'; + id: number; +}; + +export type ManageTaskModulesNextActionType = { + type: 'ENABLE_NEXT_TASK_MODULE'; +}; + +export type ManageTaskModulesResetActionType = { + type: 'RESET_TO_INITIAL_STATE'; +}; + +export type ManageTaskModulesActionType = + | ManageTaskModulesUpdateActionType + | ManageTaskModulesNextActionType + | ManageTaskModulesResetActionType; + +export type ManageTaskModulesState = { + activeTaskModuleID: number; + progressTaskModuleID: number; +}; + +export type Context = { + [key: number]: { + name: string; + data: TValues; + isDisabled: boolean; + }; +}; + +export type UseGuidedWorkflowContextType = { + state: Context; + updateDataByID: (id: number, data: TValues) => void; +}; + +export type UseManageTaskModulesType = { + taskModulesState: ManageTaskModulesState; + updateActiveTaskModule: (taskNumber: number) => void; + enableNextTaskModule: () => void; +}; diff --git a/src/components/SQFormGuidedWorkflow/index.js b/src/components/SQFormGuidedWorkflow/index.ts similarity index 100% rename from src/components/SQFormGuidedWorkflow/index.js rename to src/components/SQFormGuidedWorkflow/index.ts diff --git a/src/components/SQFormGuidedWorkflow/useGuidedWorkflowContext.js b/src/components/SQFormGuidedWorkflow/useGuidedWorkflowContext.js deleted file mode 100644 index 4c2998b4..00000000 --- a/src/components/SQFormGuidedWorkflow/useGuidedWorkflowContext.js +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react'; - -export function useGuidedWorkflowContext(taskModules) { - const initialData = taskModules.reduce((acc, taskModule, index) => { - return { - ...acc, - [index + 1]: { - name: taskModule.name, - data: taskModule.formikProps.initialValues, - isDisabled: taskModule.isDisabled || false, - }, - }; - }, {}); - - const reducer = (prevState, action) => { - switch (action.type) { - case 'UPDATE': - return taskModules.reduce((acc, taskModlue, index) => { - const taskID = index + 1; - const prevData = prevState[taskID].data; - return { - ...acc, - [taskID]: { - name: taskModlue.name, - data: action.id === taskID ? action.data : prevData, - isDisabled: taskModlue.isDisabled || false, - }, - }; - }, {}); - default: - throw new Error( - `The ${action.type} type provided to useGuidedWorkflow is not valid` - ); - } - }; - - const [state, dispatch] = React.useReducer(reducer, initialData); - - const updateDataByID = (id, data) => { - dispatch({type: 'UPDATE', id, data}); - }; - - return [state, updateDataByID]; -} diff --git a/src/components/SQFormGuidedWorkflow/useGuidedWorkflowContext.ts b/src/components/SQFormGuidedWorkflow/useGuidedWorkflowContext.ts new file mode 100644 index 00000000..ff526378 --- /dev/null +++ b/src/components/SQFormGuidedWorkflow/useGuidedWorkflowContext.ts @@ -0,0 +1,61 @@ +import React from 'react'; +import type { + TaskModuleProps, + Context, + GuidedWorkflowDispatchActionType, + UseGuidedWorkflowContextType, +} from './Types'; + +export function useGuidedWorkflowContext( + taskModules: Array> +): UseGuidedWorkflowContextType { + const initialData = taskModules.reduce>( + (acc, taskModule, index) => { + return { + ...acc, + [index + 1]: { + name: taskModule.name, + data: taskModule.formikProps.initialValues, + isDisabled: taskModule.isDisabled || false, + }, + }; + }, + {} + ); + + const reducer = ( + prevState: Context, + action: GuidedWorkflowDispatchActionType + ) => { + switch (action.type) { + case 'UPDATE': + return taskModules.reduce>( + (acc, taskModlue, index) => { + const taskID = index + 1; + const prevData = prevState[taskID].data; + return { + ...acc, + [taskID]: { + name: taskModlue.name, + data: action.id === taskID ? action.data : prevData, + isDisabled: taskModlue.isDisabled || false, + }, + }; + }, + {} + ); + default: + throw new Error( + `The ${action.type} type provided to useGuidedWorkflow is not valid` + ); + } + }; + + const [state, dispatch] = React.useReducer(reducer, initialData); + + const updateDataByID = (id: keyof typeof initialData, data: TValues) => { + dispatch({type: 'UPDATE', id, data}); + }; + + return {state, updateDataByID}; +} diff --git a/src/components/SQFormGuidedWorkflow/useManageTaskModules.js b/src/components/SQFormGuidedWorkflow/useManageTaskModules.ts similarity index 72% rename from src/components/SQFormGuidedWorkflow/useManageTaskModules.js rename to src/components/SQFormGuidedWorkflow/useManageTaskModules.ts index 239ef076..3822220b 100644 --- a/src/components/SQFormGuidedWorkflow/useManageTaskModules.js +++ b/src/components/SQFormGuidedWorkflow/useManageTaskModules.ts @@ -1,16 +1,25 @@ import React from 'react'; - -const getInitialTaskModuleID = (initialCompletedTasks, firstTaskModuleID) => { - if (!!initialCompletedTasks) { +import type { + Context, + ManageTaskModulesActionType, + ManageTaskModulesState, + UseManageTaskModulesType, +} from './Types'; + +const getInitialTaskModuleID = ( + initialCompletedTasks: number, + firstTaskModuleID: number +) => { + if (initialCompletedTasks) { return initialCompletedTasks + 1; } return firstTaskModuleID; }; -export function useManageTaskModules( - initialCompletedTasks, - taskModulesContext -) { +export function useManageTaskModules( + initialCompletedTasks: number, + taskModulesContext: Context +): UseManageTaskModulesType { const taskModulesLength = Object.keys(taskModulesContext).length; const firstTaskModuleID = Number(Object.keys(taskModulesContext)[0]); const initialTaskModuleID = getInitialTaskModuleID( @@ -25,18 +34,25 @@ export function useManageTaskModules( progressTaskModuleID: initialTaskModuleID, }; - const manageTaskModulesReducer = (prevState, action) => { - const isTaskIDTheFinalTask = (prevTaskID) => { + const manageTaskModulesReducer = ( + prevState: ManageTaskModulesState, + action: ManageTaskModulesActionType + ): ManageTaskModulesState => { + const isTaskIDTheFinalTask = (prevTaskID: number | undefined) => { return prevTaskID === taskModulesLength; }; - const isTaskDisabled = (taskID) => { - return taskModulesContext[taskID].isDisabled; + const isTaskDisabled = (taskID: number | undefined) => { + if (taskID) { + return taskModulesContext[taskID].isDisabled; + } + + return true; }; - const findNextTaskID = (prevTaskID) => { + const findNextTaskID = (prevTaskID: number): number => { if (isTaskIDTheFinalTask(prevTaskID)) { - return; + return -1; } const nextTaskID = prevTaskID + 1; @@ -48,14 +64,14 @@ export function useManageTaskModules( return nextTaskID; }; - const incrementTaskModuleID = (prevTaskID) => { + const incrementTaskModuleID = (prevTaskID: number) => { if (isTaskIDTheFinalTask(prevTaskID)) { return prevTaskID; } const nextTaskModuleID = findNextTaskID(prevTaskID); - if (nextTaskModuleID) { + if (nextTaskModuleID !== -1) { return nextTaskModuleID; } @@ -78,8 +94,13 @@ export function useManageTaskModules( }; }; - const updateActiveTaskModuleID = (id) => { + const updateActiveTaskModuleID = (id: number) => { const nextID = findNextTaskID(id); + if (nextID === -1) { + return { + ...prevState, + }; + } if (isTaskDisabled(nextID)) { const nextTaskID = findNextTaskID(nextID); @@ -114,7 +135,7 @@ export function useManageTaskModules( initialState ); - const updateActiveTaskModule = (taskNumber) => { + const updateActiveTaskModule = (taskNumber: number) => { if (taskModulesState.activeTaskModuleID !== taskNumber) { updateTaskModulesState({ type: 'UPDATE_ACTIVE_TASK_MODULE', diff --git a/stories/SQFormGuidedWorkflow.stories.js b/stories/SQFormGuidedWorkflow.stories.js index 03aa854b..93d6d18b 100644 --- a/stories/SQFormGuidedWorkflow.stories.js +++ b/stories/SQFormGuidedWorkflow.stories.js @@ -197,7 +197,7 @@ const Template = () => { ), title: 'Providers', - actionButton: ( + actions: ( window.alert('Awesome list here')} diff --git a/stories/__tests__/SQFormGuidedWorkflow.stories.test.js b/stories/__tests__/SQFormGuidedWorkflow.stories.test.js index f4c3bc16..f297d568 100644 --- a/stories/__tests__/SQFormGuidedWorkflow.stories.test.js +++ b/stories/__tests__/SQFormGuidedWorkflow.stories.test.js @@ -58,7 +58,7 @@ describe('SQFormGuidedWorkflow Tests', () => { expect(nextButton).toBeDisabled(); }); - it('should enable next button when all required fields are filled out in the 1st section', () => { + it('should enable next button when all required fields are filled out in the 1st section', async () => { render(); const nextButton = screen.getByRole('button', {name: /form submission/i}); @@ -70,7 +70,9 @@ describe('SQFormGuidedWorkflow Tests', () => { const interested = screen.getByText('Interested'); userEvent.click(interested); - expect(nextButton).toBeEnabled(); + await waitFor(() => { + expect(nextButton).toBeEnabled(); + }); }); it('should close current expanded card and open the next one when next button is clicked', async () => { @@ -89,6 +91,9 @@ describe('SQFormGuidedWorkflow Tests', () => { userEvent.click(interested); const nextButton = screen.getByRole('button', {name: /form submission/i}); + await waitFor(() => { + expect(nextButton).toBeEnabled(); + }); userEvent.click(nextButton); await waitForElementToBeRemoved(screen.getByTestId('loadingSpinner'), { @@ -114,6 +119,9 @@ describe('SQFormGuidedWorkflow Tests', () => { userEvent.click(interested); const nextButton = screen.getByRole('button', {name: /form submission/i}); + await waitFor(() => { + expect(nextButton).toBeEnabled(); + }); userEvent.click(nextButton); await waitForElementToBeRemoved(screen.getByTestId('loadingSpinner'), { @@ -145,6 +153,9 @@ describe('SQFormGuidedWorkflow Tests', () => { const interested = screen.getByText('Interested'); userEvent.click(interested); const nextButton = screen.getByRole('button', {name: /form submission/i}); + await waitFor(() => { + expect(nextButton).toBeEnabled(); + }); userEvent.click(nextButton); await waitForElementToBeRemoved(screen.getByTestId('loadingSpinner'), { timeout: 3500, @@ -195,6 +206,9 @@ describe('Testing new story', () => { userEvent.type(textbox, 'Hello'); const nextButton = screen.getByRole('button', {name: /form submission/i}); + await waitFor(() => { + expect(nextButton).toBeEnabled(); + }); userEvent.click(nextButton); await waitFor(