diff --git a/jest.config.js b/jest.config.js index 3d9c6751..da199fd5 100644 --- a/jest.config.js +++ b/jest.config.js @@ -7,5 +7,9 @@ module.exports = { '^.+\\.(t|j)sx?$': 'ts-jest' }, setupFilesAfterEnv: ['/packages/react-sdk-components/tests/setupTests.js'], - coverageDirectory: 'tests/coverage' + moduleNameMapper: { + '\\.css$': 'identity-obj-proxy' // Mock CSS imports + }, + coverageDirectory: 'tests/coverage', + watchman: false }; diff --git a/package.json b/package.json index 722d3329..fd478c64 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,6 @@ "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-sonarjs": "^3.0.5", - "typescript-eslint": "^8.45.0", "html-webpack-plugin": "^5.6.4", "http-server": "^14.1.1", "istanbul": "^0.4.5", @@ -119,6 +118,7 @@ "ts-jest": "^29.4.1", "ts-loader": "^9.5.2", "typescript": "^5.9.2", + "typescript-eslint": "^8.45.0", "webpack": "^5.101.2", "webpack-cli": "^6.0.1", "webpack-dev-server": "^5.2.2" diff --git a/packages/react-sdk-components/src/components/field/AutoComplete/AutoComplete.tsx b/packages/react-sdk-components/src/components/field/AutoComplete/AutoComplete.tsx index b4c0f54d..abfa8815 100644 --- a/packages/react-sdk-components/src/components/field/AutoComplete/AutoComplete.tsx +++ b/packages/react-sdk-components/src/components/field/AutoComplete/AutoComplete.tsx @@ -71,7 +71,8 @@ export default function AutoComplete(props: AutoCompleteProps) { status, helperText, hideLabel, - onRecordChange + onRecordChange, + disabled } = props; const context = getPConnect().getContextName(); @@ -210,6 +211,7 @@ export default function AutoComplete(props: AutoCompleteProps) { required={required} error={status === 'error'} label={label} + disabled={disabled} data-test-id={testId} /> )} diff --git a/packages/react-sdk-components/src/components/field/Checkbox/Checkbox.tsx b/packages/react-sdk-components/src/components/field/Checkbox/Checkbox.tsx index 4306370c..95a9546d 100644 --- a/packages/react-sdk-components/src/components/field/Checkbox/Checkbox.tsx +++ b/packages/react-sdk-components/src/components/field/Checkbox/Checkbox.tsx @@ -221,7 +221,7 @@ export default function CheckboxComponent(props: CheckboxProps) { onBlur={() => { thePConn.getValidationApi().validate(selectedvalues, selectionList); }} - data-testid={`${testId}:${element.value}`} + data-test-id={`${testId}:${element.value}`} /> } key={index} diff --git a/packages/react-sdk-components/src/components/field/DateTime/DateTime.tsx b/packages/react-sdk-components/src/components/field/DateTime/DateTime.tsx index 1afc9e0e..9f663178 100644 --- a/packages/react-sdk-components/src/components/field/DateTime/DateTime.tsx +++ b/packages/react-sdk-components/src/components/field/DateTime/DateTime.tsx @@ -63,7 +63,7 @@ export default function DateTime(props: DateTimeProps) { const testProps: any = { 'data-test-id': testId }; const handleChange = (date) => { - const timeZoneDateTime = (dayjs as any).tz(date.format('YYYY-MM-DDTHH:mm:ss'), timezone); + const timeZoneDateTime = (dayjs as any).tz(date?.format('YYYY-MM-DDTHH:mm:ss'), timezone); const changeValue = timeZoneDateTime && timeZoneDateTime.isValid() ? timeZoneDateTime.toISOString() : ''; setDateValue(timeZoneDateTime); handleEvent(actions, 'changeNblur', propName, changeValue); diff --git a/packages/react-sdk-components/src/components/field/RadioButtons/RadioButtons.tsx b/packages/react-sdk-components/src/components/field/RadioButtons/RadioButtons.tsx index 33228f9a..c9b5c687 100644 --- a/packages/react-sdk-components/src/components/field/RadioButtons/RadioButtons.tsx +++ b/packages/react-sdk-components/src/components/field/RadioButtons/RadioButtons.tsx @@ -44,7 +44,8 @@ export default function RadioButtons(props: RadioButtonsProps) { datasource, imagePosition, imageSize, - showImageDescription + showImageDescription, + disabled } = props; const [theSelectedButton, setSelectedButton] = useState(value); @@ -147,7 +148,7 @@ export default function RadioButtons(props: RadioButtonsProps) { localePath, thePConn.getLocaleRuleNameFromKeys(localeClass, localeContext, localeName) )} - control={} + control={} /> ); })} diff --git a/packages/react-sdk-components/tests/unit/components/forms/Autocomplete/index.test.tsx b/packages/react-sdk-components/tests/unit/components/forms/Autocomplete/index.test.tsx new file mode 100644 index 00000000..ef2b0f11 --- /dev/null +++ b/packages/react-sdk-components/tests/unit/components/forms/Autocomplete/index.test.tsx @@ -0,0 +1,162 @@ +import React from 'react'; +import { render, fireEvent, screen } from '@testing-library/react'; +import AutoComplete from '../../../../../src/components/field/AutoComplete'; +import TextInput from '../../../../../src/components/field/TextInput'; +import handleEvent from '../../../../../src/components/helpers/event-utils'; + +jest.mock('../../../../../src/components/helpers/event-utils'); +jest.mock('../../../../../src/components/helpers/utils', () => ({ + getDataPage: jest.fn(), + getOptionList: jest.fn((value) => { + return value.datasource; + }) +})); +jest.mock('../../../../../src/bridge/helpers/sdk_component_map', () => ({ + getComponentFromMap: jest.fn(() => require('../FieldValueList').default) +})); + +const updateFieldValue = jest.fn(); +const triggerFieldChange = jest.fn(); +const updateDirtyCheckChangeList = jest.fn(); +const validate = jest.fn(); +const clearErrorMessages = jest.fn(); +const [ignoreSuggestion, acceptSuggestion] = [jest.fn(), jest.fn()]; +const defaultProps = { + getPConnect: jest.fn( + () => + ({ + getActionsApi: () => ({ updateFieldValue, triggerFieldChange }), + getStateProps: () => ({ + value: '.autoComplete' + }), + getValidationApi: () => ({ + validate + }), + getContextName() { + return 'app/primary_1/workarea_1'; + }, + getDataObject() { + return; + }, + updateDirtyCheckChangeList, + clearErrorMessages, + ignoreSuggestion, + acceptSuggestion + }) as any + ), + label: 'AutoComplete', + required: true, + disabled: false, + value: '', + validatemessage: '', + status: '', + readOnly: false, + testId: 'autoCompleteTestId', + fieldMetadata: {}, + helperText: '', + displayMode: '', + hideLabel: false, + placeholder: '', + onChange: jest.fn(), + listType: 'associated', + columns: [], + datasource: [ + { key: 'option1', value: 'Option 1' }, + { key: 'option2', value: 'Option 2' }, + { key: 'option3', value: 'Option 3' }, + { key: 'option4', value: 'Option 4' } + ] +}; + +describe('AutoComplete Component', () => { + test('renders with required attribute', () => { + const props = { ...defaultProps }; + props.required = true; + const { rerender } = render(); + expect(screen.getAllByText('AutoComplete')[0]).toHaveClass('Mui-required'); + + props.required = false; + rerender(); + expect(screen.getAllByText('AutoComplete')[1]).not.toHaveAttribute('Mui-required'); + }); + + test('renders with disabled attribute', () => { + const props = { ...defaultProps }; + props.disabled = true; + const { rerender } = render(); + expect(screen.getByText('AutoComplete')).toHaveClass('Mui-disabled'); + + props.disabled = false; + rerender(); + expect(screen.getByText('AutoComplete')).not.toHaveClass('disabled'); + }); + + test('renders with readOnly attribute', () => { + const props = { ...defaultProps }; + props.readOnly = true; + const { getByTestId, rerender } = render(); + expect(getByTestId('autoCompleteTestId')).toHaveAttribute('readonly'); + + props.readOnly = false; + rerender(); + expect(getByTestId('autoCompleteTestId')).not.toHaveAttribute('readonly'); + }); + + test('renders with label', () => { + const props = { ...defaultProps }; + const { getByText } = render(); + expect(getByText('AutoComplete')).toBeVisible(); + }); + + test('renders in DISPLAY_ONLY mode', () => { + const props = { ...defaultProps }; + props.displayMode = 'DISPLAY_ONLY'; + props.value = 'Hi there!'; + const { getByText } = render(); + expect(getByText('Hi there!')).toBeVisible(); + }); + + test('renders in STACKED_LARGE_VAL mode', () => { + const props = { ...defaultProps }; + props.displayMode = 'STACKED_LARGE_VAL'; + props.value = 'Hi there!'; + const { getByText } = render(); + expect(getByText('Hi there!')).toBeVisible(); + }); + + test('does not invoke onBlur handler for readOnly fields', () => { + const props = { ...defaultProps }; + props.readOnly = true; + const { getByTestId } = render(); + fireEvent.change(getByTestId('autoCompleteTestId'), { target: { value: 'Option 1' } }); + fireEvent.blur(getByTestId('autoCompleteTestId')); + expect(handleEvent).not.toHaveBeenCalled(); + }); + + test('invokes handlers for blur and change events', () => { + const props = { ...defaultProps }; + const { getByRole, getByText } = render(); + const input = getByRole('combobox'); + fireEvent.change(input, { target: { value: 'Option 1' } }); + fireEvent.click(getByText('Option 1')); + fireEvent.blur(input); + expect(handleEvent).toHaveBeenCalled(); + }); + + test('invokes handlers for blur and change events', () => { + const props = { ...defaultProps }; + const { getByRole } = render(); + const input = getByRole('combobox'); + fireEvent.change(input, { target: { value: 'Option 1' } }); + fireEvent.blur(input); + expect(handleEvent).toHaveBeenCalled(); + }); + + test('handles input value changes correctly', () => { + const props = { ...defaultProps }; + const { getByRole, getByText } = render(); + const input = getByRole('combobox'); + fireEvent.change(input, { target: { value: 'Option 1' } }); + expect(getByText('Option 1')).toBeVisible(); + }); +}); diff --git a/packages/react-sdk-components/tests/unit/components/forms/Checkbox/index.test.tsx b/packages/react-sdk-components/tests/unit/components/forms/Checkbox/index.test.tsx new file mode 100644 index 00000000..fb7de24c --- /dev/null +++ b/packages/react-sdk-components/tests/unit/components/forms/Checkbox/index.test.tsx @@ -0,0 +1,252 @@ +import React from 'react'; +import { render, fireEvent, screen, cleanup } from '@testing-library/react'; +import CheckboxComponent from '../../../../../src/components/field/Checkbox'; + +const handleEvent = jest.fn(); +const validate = jest.fn(); +const clearErrorMessages = jest.fn(); +const insertInstruction = jest.fn(); +const deleteInstruction = jest.fn(); +const updateNewInstuctions = jest.fn(); + +jest.mock('../../../../../src/components/helpers/event-utils', () => ({ + __esModule: true, + default: (...args) => handleEvent(...args) +})); + +jest.mock('../../../../../src/components/helpers/instructions-utils', () => ({ + __esModule: true, + insertInstruction: (...args) => insertInstruction(...args), + deleteInstruction: (...args) => deleteInstruction(...args), + updateNewInstuctions: (...args) => updateNewInstuctions(...args) +})); + +jest.mock('../../../../../src/bridge/helpers/sdk_component_map', () => ({ + __esModule: true, + getComponentFromMap: (name) => { + if (name === 'SelectableCard') { + return (props) => ( +
+ +
+ ); + } + if (name === 'FieldValueList') { + return require('../FieldValueList').default; + } + return null; + } +})); + +const getPConnect = jest.fn( + () => + ({ + getActionsApi: () => ({}), + getStateProps: () => ({ value: '.checkbox' }), + getValidationApi: () => ({ validate }), + clearErrorMessages, + setReferenceList: jest.fn(), + getRawMetadata: () => ({ config: { imageDescription: 'data.description' } }) + }) as any +); + +const defaultProps = { + getPConnect, + label: 'Checkbox Label', + caption: 'Checkbox Caption', + value: true, + readOnly: false, + testId: 'checkboxTestId', + required: true, + disabled: false, + status: '', + helperText: 'Checkbox helper text', + validatemessage: '', + displayMode: '', + hideLabel: false, + trueLabel: 'Yes', + falseLabel: 'No', + selectionMode: '', + datasource: { + source: [{ key: 'opt1', value: 'Option 1', text: 'Option 1' }] + }, + selectionKey: 'data.key', + selectionList: 'selectionList', + primaryField: 'data.text', + referenceList: '', + readonlyContextList: [{ key: '' }], + variant: '', + hideFieldLabels: false, + additionalProps: {}, + imagePosition: 'left', + imageSize: 'medium', + showImageDescription: 'true', + renderMode: '', + image: 'data.image', + onChange: jest.fn() +}; + +describe('CheckboxComponent', () => { + afterEach(() => { + cleanup(); + jest.clearAllMocks(); + }); + + test('renders with label and helper text', () => { + const props = { ...defaultProps }; + render(); + expect(screen.getByText('Checkbox Label')).toBeInTheDocument(); + expect(screen.getByText('Checkbox helper text')).toBeInTheDocument(); + }); + + test('renders with required attribute', () => { + const props = { ...defaultProps }; + props.required = true; + const { rerender } = render(); + expect(screen.getByText('Checkbox Label')).toBeVisible(); + expect(screen.getByText('Checkbox Label').closest('legend')).toHaveClass('Mui-required'); + + props.required = false; + rerender(); + expect(screen.getByText('Checkbox Label').closest('legend')).not.toHaveClass('Mui-required'); + }); + + test('renders with disabled attribute', () => { + const props = { ...defaultProps }; + props.required = true; + props.disabled = true; + render(); + const checkbox = screen.getByRole('checkbox'); + expect(checkbox).toBeDisabled(); + }); + + test('renders with readOnly attribute', () => { + const props = { ...defaultProps }; + props.readOnly = true; + render(); + const checkbox = screen.getByRole('checkbox'); + expect(checkbox).toHaveAttribute('readonly'); + }); + + test('renders with validation message overriding helper text', () => { + const props = { ...defaultProps }; + props.validatemessage = 'Validation error'; + render(); + expect(screen.getByText('Validation error')).toBeVisible(); + expect(screen.queryByText('Checkbox helper text')).not.toBeInTheDocument(); + }); + + test('calls handleEvent on change when not readOnly', () => { + const props = { ...defaultProps }; + render(); + const checkbox = screen.getByRole('checkbox'); + fireEvent.click(checkbox); + expect(handleEvent).toHaveBeenCalledWith(expect.any(Object), 'changeNblur', '.checkbox', false); + }); + + test('calls validate on blur when not readOnly', () => { + const props = { ...defaultProps }; + render(); + const checkbox = screen.getByRole('checkbox'); + fireEvent.blur(checkbox); + expect(validate).toHaveBeenCalledWith(true); + }); + + test('does not call handleEvent or validate when readOnly', () => { + const props = { ...defaultProps }; + props.readOnly = true; + render(); + const checkbox = screen.getByRole('checkbox'); + fireEvent.click(checkbox); + fireEvent.blur(checkbox); + expect(handleEvent).not.toHaveBeenCalled(); + expect(validate).not.toHaveBeenCalled(); + }); + + test('renders in DISPLAY_ONLY mode', () => { + const props = { ...defaultProps }; + props.displayMode = 'DISPLAY_ONLY'; + props.value = true; + render(); + expect(screen.getByTestId('field-value-list')).toHaveTextContent('Checkbox CaptionYes'); + }); + + test('renders in STACKED_LARGE_VAL mode', () => { + const props = { ...defaultProps }; + props.displayMode = 'STACKED_LARGE_VAL'; + props.value = false; + render(); + expect(screen.getByTestId('field-value-list')).toHaveTextContent('Checkbox CaptionNo'); + }); + + test('renders multi-selection checkboxes and handles selection', () => { + const props = { ...defaultProps }; + props.selectionMode = 'multi'; + props.datasource = { + source: [ + { key: 'opt1', value: 'Option 1', text: 'Option 1' }, + { key: 'opt2', value: 'Option 2', text: 'Option 2' } + ] + }; + props.readonlyContextList = [{ key: 'opt1' }]; + const { getByLabelText } = render(); + const checkbox1 = screen.getByTestId('checkboxTestId:Option 1'); + const checkbox2 = screen.getByTestId('checkboxTestId:Option 2'); + expect(getByLabelText('Option 1').closest('input')).toBeChecked(); + expect(checkbox2).not.toBeChecked(); + + fireEvent.click(checkbox2); + expect(insertInstruction).toHaveBeenCalled(); + fireEvent.click(checkbox1); + expect(deleteInstruction).toHaveBeenCalled(); + }); + + // Card variant tests + test('renders SelectableCard when variant is card', () => { + const props = { ...defaultProps }; + props.variant = 'card'; + render(); + expect(screen.getByTestId(`selectable-card-${props.testId}`)).toBeInTheDocument(); + }); + + test('handles card option selection', () => { + const props = { ...defaultProps }; + props.variant = 'card'; + render(); + const cardOption = screen.getByTestId(`card-option-${props.testId}-opt1`); + fireEvent.click(cardOption); + expect(insertInstruction).toHaveBeenCalledWith(expect.any(Object), props.selectionList, props.selectionKey, props.primaryField, { + id: 'opt1', + primary: 'opt1' + }); + }); + + test('validates on blur in card variant', () => { + const props = { ...defaultProps }; + props.variant = 'card'; + props.readonlyContextList = []; // ✅ ensure this is defined + render(); + const cardOption = screen.getByTestId(`card-option-${props.testId}-opt1`); + fireEvent.click(cardOption); + expect(validate).toHaveBeenCalledWith([], props.selectionList); + }); + + test('renders label in card variant', () => { + const props = { ...defaultProps }; + props.variant = 'card'; + render(); + expect(screen.getByText('Checkbox Label')).toBeInTheDocument(); + }); +}); diff --git a/packages/react-sdk-components/tests/unit/components/forms/Currency/index.test.tsx b/packages/react-sdk-components/tests/unit/components/forms/Currency/index.test.tsx new file mode 100644 index 00000000..ce7fa2ef --- /dev/null +++ b/packages/react-sdk-components/tests/unit/components/forms/Currency/index.test.tsx @@ -0,0 +1,147 @@ +import React from 'react'; +import { render, fireEvent, screen } from '@testing-library/react'; +import Currency from '../../../../../src/components/field/Currency'; +import handleEvent from '../../../../../src/components/helpers/event-utils'; +import { getLocale } from '../../../../../src/components/helpers/formatters/common'; + +declare global { + interface Window { + PCore: any; + } +} + +window.PCore = { + ...window.PCore, + getEnvironmentInfo: (): any => { + return { + getUseLocale: () => { + return 'en-GB'; + }, + getLocale: () => { + return 'en-GB'; + } + }; + } +}; + +jest.mock('../../../../../src/components/helpers/event-utils'); + +jest.mock('../../../../../src/bridge/helpers/sdk_component_map', () => ({ + getComponentFromMap: jest.fn(() => require('../FieldValueList').default) +})); + +const updateFieldValue = jest.fn(); +const triggerFieldChange = jest.fn(); +const updateDirtyCheckChangeList = jest.fn(); +const validate = jest.fn(); +const clearErrorMessages = jest.fn(); +const [ignoreSuggestion, acceptSuggestion] = [jest.fn(), jest.fn()]; +const defaultProps = { + getPConnect: jest.fn( + () => + ({ + getActionsApi: () => ({ updateFieldValue, triggerFieldChange }), + getStateProps: () => ({ + value: '.currency' + }), + getValidationApi: () => ({ + validate + }), + getComponentName: () => 'Currency', + updateDirtyCheckChangeList, + clearErrorMessages, + ignoreSuggestion, + acceptSuggestion + }) as any + ), + label: 'Currency Label', + required: true, + disabled: false, + value: '', + validatemessage: '', + status: '', + readOnly: false, + testId: 'currencyTestId', + fieldMetadata: {}, + helperText: '', + displayMode: '', + hideLabel: false, + placeholder: '', + currencyISOCode: 'USD', + allowDecimals: true, + onChange: jest.fn(), + formatter: '' +}; + +describe('Currency Component', () => { + test('renders with required attribute', () => { + const props = { ...defaultProps }; + const { getByTestId, rerender } = render(); + expect(getByTestId('currencyTestId')).toHaveAttribute('required'); + + props.required = false; + rerender(); + expect(getByTestId('currencyTestId')).not.toHaveAttribute('required'); + }); + + test('renders with disabled attribute', () => { + const props = { ...defaultProps }; + props.disabled = true; + const { getByTestId, rerender } = render(); + expect(getByTestId('currencyTestId')).toHaveAttribute('disabled'); + + props.disabled = false; + rerender(); + expect(getByTestId('currencyTestId')).not.toHaveAttribute('disabled'); + }); + + test('renders with readOnly attribute', () => { + const props = { ...defaultProps }; + props.readOnly = true; + const { getByTestId, rerender } = render(); + expect(getByTestId('currencyTestId')).toHaveAttribute('readonly'); + + props.readOnly = false; + rerender(); + expect(getByTestId('currencyTestId')).not.toHaveAttribute('readonly'); + }); + + test('renders with label', () => { + const props = { ...defaultProps }; + const { getByText } = render(); + expect(getByText('Currency Label')).toBeVisible(); + }); + + test('renders in DISPLAY_ONLY mode', () => { + const props = { ...defaultProps }; + props.displayMode = 'DISPLAY_ONLY'; + props.value = '1000'; + const { getByText } = render(); + expect(getByText('$1,000.00')).toBeVisible(); + }); + + test('renders in STACKED_LARGE_VAL mode', () => { + const props = { ...defaultProps }; + props.displayMode = 'STACKED_LARGE_VAL'; + props.value = '1000'; + const { getByText } = render(); + expect(getByText('$1,000.00')).toBeVisible(); + }); + + test('does not invoke onBlur handler for readOnly fields', () => { + const props = { ...defaultProps }; + props.readOnly = true; + const { getByTestId } = render(); + fireEvent.change(getByTestId('currencyTestId'), { target: { value: '1000' } }); + fireEvent.blur(getByTestId('currencyTestId')); + expect(handleEvent).not.toHaveBeenCalled(); + }); + + test('invokes handlers for blur and change events', () => { + const props = { ...defaultProps }; + const { getByTestId } = render(); + fireEvent.change(getByTestId('currencyTestId'), { target: { value: '1000' } }); + fireEvent.blur(getByTestId('currencyTestId')); + expect(handleEvent).toHaveBeenCalled(); + }); +}); diff --git a/packages/react-sdk-components/tests/unit/components/forms/Date/index.test.tsx b/packages/react-sdk-components/tests/unit/components/forms/Date/index.test.tsx new file mode 100644 index 00000000..a31aafc7 --- /dev/null +++ b/packages/react-sdk-components/tests/unit/components/forms/Date/index.test.tsx @@ -0,0 +1,157 @@ +import React from 'react'; +import { render, fireEvent, screen } from '@testing-library/react'; +import Date from '../../../../../src/components/field/Date'; +import handleEvent from '../../../../../src/components/helpers/event-utils'; +import TextInput from '../../../../../src/components/field/TextInput'; +import { LocalizationProvider } from '@mui/x-date-pickers'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import dayjs from 'dayjs'; + +jest.mock('../../../../../src/components/helpers/event-utils'); + +jest.mock('../../../../../src/bridge/helpers/sdk_component_map', () => ({ + getComponentFromMap: jest.fn(() => require('../FieldValueList').default) +})); + +declare global { + interface Window { + PCore: any; + } +} + +window.PCore = { + ...window.PCore, + getEnvironmentInfo: (): any => { + return { + getUseLocale: () => { + return 'en-US'; + }, + getLocale: () => { + return 'en-US'; + } + }; + }, + getLocaleUtils(): any { + return { + getLocaleValue: (value) => { + return value; + } + }; + } +}; + +const updateFieldValue = jest.fn(); +const triggerFieldChange = jest.fn(); +const updateDirtyCheckChangeList = jest.fn(); +const validate = jest.fn(); +const clearErrorMessages = jest.fn(); +const [ignoreSuggestion, acceptSuggestion] = [jest.fn(), jest.fn()]; +const defaultProps = { + getPConnect: jest.fn( + () => + ({ + getActionsApi: () => ({ updateFieldValue, triggerFieldChange }), + getStateProps: () => ({ + value: '.date' + }), + getValidationApi: () => ({ + validate + }), + updateDirtyCheckChangeList, + clearErrorMessages, + ignoreSuggestion, + acceptSuggestion + }) as any + ), + label: 'Date', + required: true, + disabled: false, + value: '', + validatemessage: '', + status: '', + readOnly: false, + testId: 'dateTestId', + fieldMetadata: {}, + helperText: '', + displayMode: '', + hideLabel: false, + placeholder: '', + onChange: jest.fn() +}; + +describe('Date Component', () => { + const renderWithLocalization = (ui) => { + return render({ui}); + }; + + test('renders with required attribute', () => { + const props = { ...defaultProps }; + renderWithLocalization(); + expect(screen.getAllByPlaceholderText('mm/dd/yyyy')[0]).toHaveAttribute('required'); + + props.required = false; + renderWithLocalization(); + expect(screen.getAllByPlaceholderText('mm/dd/yyyy')[1]).not.toHaveAttribute('required'); + }); + + test('renders with disabled attribute', () => { + const props = { ...defaultProps }; + props.disabled = true; + renderWithLocalization(); + expect(screen.getAllByPlaceholderText('mm/dd/yyyy')[0]).toHaveAttribute('disabled'); + + props.disabled = false; + renderWithLocalization(); + expect(screen.getAllByPlaceholderText('mm/dd/yyyy')[1]).not.toHaveAttribute('disabled'); + }); + + test('renders with readOnly attribute', () => { + const props = { ...defaultProps }; + props.readOnly = true; + const { getByTestId } = render(); + expect(getByTestId('dateTestId')).toHaveAttribute('readonly'); + + props.readOnly = false; + renderWithLocalization(); + expect(screen.getAllByPlaceholderText('mm/dd/yyyy')[0]).not.toHaveAttribute('readonly'); + }); + + test('renders with label', () => { + const props = { ...defaultProps }; + const { getByText } = renderWithLocalization(); + expect(getByText('Date')).toBeVisible(); + }); + + test('renders in DISPLAY_ONLY mode', () => { + const props = { ...defaultProps }; + props.displayMode = 'DISPLAY_ONLY'; + props.value = '2023-01-01'; + const { getByText } = renderWithLocalization(); + expect(getByText('01/01/2023')).toBeVisible(); + }); + + test('renders in STACKED_LARGE_VAL mode', () => { + const props = { ...defaultProps }; + props.displayMode = 'STACKED_LARGE_VAL'; + props.value = '2023-01-01'; + const { getByText } = renderWithLocalization(); + expect(getByText('01/01/2023')).toBeVisible(); + }); + + test('does not invoke onBlur handler for readOnly fields', () => { + const props = { ...defaultProps }; + props.readOnly = true; + render(); + fireEvent.change(screen.getByTestId('dateTestId'), { target: { value: '2023-01-01' } }); + fireEvent.blur(screen.getByTestId('dateTestId')); + expect(handleEvent).not.toHaveBeenCalled(); + }); + + test('invokes handlers for blur and change events', () => { + const props = { ...defaultProps }; + props.readOnly = false; + renderWithLocalization(); + fireEvent.click(screen.getByPlaceholderText('mm/dd/yyyy')); + fireEvent.change(screen.getByPlaceholderText('mm/dd/yyyy'), { target: { value: '2023-01-01' } }); + }); +}); diff --git a/packages/react-sdk-components/tests/unit/components/forms/DateTime/index.test.tsx b/packages/react-sdk-components/tests/unit/components/forms/DateTime/index.test.tsx new file mode 100644 index 00000000..575c6c56 --- /dev/null +++ b/packages/react-sdk-components/tests/unit/components/forms/DateTime/index.test.tsx @@ -0,0 +1,160 @@ +import React from 'react'; +import { render, fireEvent, screen } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; +import DateTime from '../../../../../src/components/field/DateTime'; +import handleEvent from '../../../../../src/components/helpers/event-utils'; +import { format } from '../../../../../src/components/helpers/formatters'; +import TextInput from '../../../../../src/components/field/TextInput'; +import { LocalizationProvider } from '@mui/x-date-pickers'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; + +jest.mock('../../../../../src/components/helpers/event-utils'); +jest.mock('../../../../../src/bridge/helpers/sdk_component_map', () => ({ + getComponentFromMap: jest.fn(() => require('../FieldValueList').default) +})); + +declare global { + interface Window { + PCore: any; + } +} + +window.PCore = { + ...window.PCore, + getEnvironmentInfo: (): any => { + return { + getUseLocale: () => { + return 'en-US'; + }, + getLocale: () => { + return 'en-US'; + }, + getTimeZone: () => 'America/New_York' + }; + }, + getLocaleUtils(): any { + return { + getLocaleValue: (value) => { + return value; + } + }; + } +}; + +const updateFieldValue = jest.fn(); +const triggerFieldChange = jest.fn(); +const validate = jest.fn(); +const clearErrorMessages = jest.fn(); + +const defaultProps = { + getPConnect: jest.fn( + () => + ({ + getActionsApi: () => ({ updateFieldValue, triggerFieldChange }), + getStateProps: () => ({ + value: '.dateTime' + }), + getValidationApi: () => ({ + validate + }), + clearErrorMessages + }) as any + ), + label: 'DateTime Label', + required: false, + disabled: false, + value: '', + validatemessage: '', + status: '', + readOnly: false, + testId: 'dateTimeTestId', + fieldMetadata: {}, + helperText: '', + displayMode: '', + hideLabel: false, + placeholder: '', + onChange: jest.fn() +}; + +describe('DateTime Component', () => { + const renderWithLocalization = (ui) => { + return render({ui}); + }; + + test('renders with required attribute', () => { + const props = { ...defaultProps }; + props.required = true; + renderWithLocalization(); + expect(screen.getAllByPlaceholderText('mm/dd/yyyy hh:mm a')[0]).toHaveAttribute('required'); + + props.required = false; + renderWithLocalization(); + expect(screen.getAllByPlaceholderText('mm/dd/yyyy hh:mm a')[1]).not.toHaveAttribute('required'); + }); + + test('renders with disabled attribute', () => { + const props = { ...defaultProps }; + props.disabled = true; + renderWithLocalization(); + expect(screen.getAllByPlaceholderText('mm/dd/yyyy hh:mm a')[0]).toHaveAttribute('disabled'); + + props.disabled = false; + renderWithLocalization(); + expect(screen.getAllByPlaceholderText('mm/dd/yyyy hh:mm a')[1]).not.toHaveAttribute('disabled'); + }); + + test('renders with readOnly attribute', () => { + const props = { ...defaultProps }; + props.readOnly = true; + const { getByTestId } = render(); + expect(getByTestId('dateTimeTestId')).toHaveAttribute('readonly'); + + props.readOnly = false; + renderWithLocalization(); + expect(screen.getByPlaceholderText('mm/dd/yyyy hh:mm a')).not.toHaveAttribute('readonly'); + }); + + test('renders with label', () => { + const props = { ...defaultProps }; + const { getByLabelText } = renderWithLocalization(); + expect(getByLabelText('DateTime Label')).toBeVisible(); + }); + + test('renders in DISPLAY_ONLY mode', () => { + const props = { ...defaultProps }; + props.displayMode = 'DISPLAY_ONLY'; + props.value = '2023-10-10T10:10:00'; + const dateValue = format(props.value, 'datetime', { + format: 'MM/DD/YYYY hh:mm a' + }); + renderWithLocalization(); + expect(screen.getByText(dateValue)).toBeVisible(); + }); + + test('renders in STACKED_LARGE_VAL mode', () => { + const props = { ...defaultProps }; + props.displayMode = 'STACKED_LARGE_VAL'; + props.value = '2023-10-10T10:10:00'; + const dateValue = format(props.value, 'datetime', { + format: 'MM/DD/YYYY hh:mm a' + }); + renderWithLocalization(); + expect(screen.getByText(dateValue)).toBeVisible(); + }); + + test('does not invoke onBlur handler for readOnly fields', () => { + const props = { ...defaultProps }; + props.readOnly = true; + render(); + fireEvent.change(screen.getByTestId('dateTimeTestId'), { target: { value: '2023-10-10T10:10:00' } }); + fireEvent.blur(screen.getByTestId('dateTimeTestId')); + expect(handleEvent).not.toHaveBeenCalled(); + }); + + test('invokes handlers for blur and change events', () => { + const props = { ...defaultProps }; + renderWithLocalization(); + fireEvent.change(screen.getByPlaceholderText('mm/dd/yyyy hh:mm a'), { target: { value: '2023-10-10T10:10:00' } }); + fireEvent.blur(screen.getByPlaceholderText('mm/dd/yyyy hh:mm a')); + }); +}); diff --git a/packages/react-sdk-components/tests/unit/components/forms/Decimal/index.test.tsx b/packages/react-sdk-components/tests/unit/components/forms/Decimal/index.test.tsx new file mode 100644 index 00000000..32eafa27 --- /dev/null +++ b/packages/react-sdk-components/tests/unit/components/forms/Decimal/index.test.tsx @@ -0,0 +1,146 @@ +import React from 'react'; +import { render, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; +import Decimal from '../../../../../src/components/field/Decimal/index'; +import handleEvent from '../../../../../src/components/helpers/event-utils'; + +declare global { + interface Window { + PCore: any; + } +} + +window.PCore = { + ...window.PCore, + getEnvironmentInfo: (): any => { + return { + getUseLocale: () => { + return 'en-US'; + }, + getLocale: () => { + return 'en-US'; + } + }; + } +}; + +jest.mock('../../../../../src/components/helpers/event-utils'); +jest.mock('../../../../../src/bridge/helpers/sdk_component_map', () => ({ + getComponentFromMap: jest.fn(() => require('../FieldValueList').default) +})); + +const updateFieldValue = jest.fn(); +const triggerFieldChange = jest.fn(); +const updateDirtyCheckChangeList = jest.fn(); +const validate = jest.fn(); +const clearErrorMessages = jest.fn(); +const [ignoreSuggestion, acceptSuggestion] = [jest.fn(), jest.fn()]; +const defaultProps = { + getPConnect: jest.fn( + () => + ({ + getActionsApi: () => ({ updateFieldValue, triggerFieldChange }), + getStateProps: () => ({ + value: '.decimal' + }), + getValidationApi: () => ({ + validate + }), + getComponentName: () => 'Decimal', + updateDirtyCheckChangeList, + clearErrorMessages, + ignoreSuggestion, + acceptSuggestion + }) as any + ), + label: 'Decimal Label', + required: false, + disabled: false, + value: '', + validatemessage: '', + status: '', + readOnly: false, + testId: 'decimalTestId', + fieldMetadata: {}, + helperText: '', + displayMode: '', + hideLabel: false, + placeholder: '', + currencyISOCode: 'USD', + decimalPrecision: 2, + showGroupSeparators: true, + formatter: '', + onChange: jest.fn() +}; + +describe('Decimal Component', () => { + test('renders with required attribute', () => { + const props = { ...defaultProps }; + props.required = true; + const { getByTestId } = render(); + expect(getByTestId('decimalTestId')).toHaveAttribute('required'); + }); + + test('renders with disabled attribute', () => { + const props = { ...defaultProps }; + props.disabled = true; + const { getByTestId, rerender } = render(); + expect(getByTestId('decimalTestId')).toHaveAttribute('disabled'); + + props.disabled = false; + rerender(); + expect(getByTestId('decimalTestId')).not.toHaveAttribute('disabled'); + }); + + test('renders with readOnly attribute', () => { + const props = { ...defaultProps }; + props.readOnly = true; + const { getByTestId, rerender } = render(); + expect(getByTestId('decimalTestId')).toHaveAttribute('readonly'); + + props.readOnly = false; + rerender(); + expect(getByTestId('decimalTestId')).not.toHaveAttribute('readonly'); + }); + + test('renders with label', () => { + const props = { ...defaultProps }; + const { getAllByText } = render(); + const labels = getAllByText('Decimal Label'); + expect(labels.length).toBeGreaterThan(0); + expect(labels[0]).toBeVisible(); + }); + + test('renders in DISPLAY_ONLY mode', () => { + const props = { ...defaultProps }; + props.displayMode = 'DISPLAY_ONLY'; + props.value = '1234.56'; + const { getByText } = render(); + expect(getByText('1,234.56')).toBeVisible(); + }); + + test('renders in STACKED_LARGE_VAL mode', () => { + const props = { ...defaultProps }; + props.displayMode = 'STACKED_LARGE_VAL'; + props.value = '1234.56'; + const { getByText } = render(); + expect(getByText('1,234.56')).toBeVisible(); + }); + + test('does not invoke onBlur handler for readOnly fields', () => { + const props = { ...defaultProps }; + props.readOnly = true; + const { getByTestId } = render(); + fireEvent.change(getByTestId('decimalTestId'), { target: { value: '1234.56' } }); + fireEvent.blur(getByTestId('decimalTestId')); + expect(handleEvent).not.toHaveBeenCalled(); + }); + + test('invokes handlers for blur and change events', () => { + const props = { ...defaultProps }; + const { getByTestId } = render(); + fireEvent.change(getByTestId('decimalTestId'), { target: { value: '1234.56' } }); + fireEvent.blur(getByTestId('decimalTestId')); + expect(handleEvent).toHaveBeenCalled(); + }); +}); diff --git a/packages/react-sdk-components/tests/unit/components/forms/Dropdown/index.test.tsx b/packages/react-sdk-components/tests/unit/components/forms/Dropdown/index.test.tsx new file mode 100644 index 00000000..3a279ad1 --- /dev/null +++ b/packages/react-sdk-components/tests/unit/components/forms/Dropdown/index.test.tsx @@ -0,0 +1,159 @@ +import React from 'react'; +import { render, fireEvent, screen } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; +import Dropdown from '../../../../../src/components/field/Dropdown'; +import handleEvent from '../../../../../src/components/helpers/event-utils'; +import TextInput from '../../../../../src/components/field/TextInput'; + +jest.mock('../../../../../src/components/helpers/event-utils'); +jest.mock('../../../../../src/components/helpers/utils', () => ({ + getDataPage: jest.fn(), + getOptionList: jest.fn((value) => { + return value.datasource; + }) +})); +jest.mock('../../../../../src/bridge/helpers/sdk_component_map', () => ({ + getComponentFromMap: jest.fn(() => require('../FieldValueList').default) +})); + +const updateFieldValue = jest.fn(); +const triggerFieldChange = jest.fn(); +const updateDirtyCheckChangeList = jest.fn(); +const validate = jest.fn(); +const clearErrorMessages = jest.fn(); +const [ignoreSuggestion, acceptSuggestion] = [jest.fn(), jest.fn()]; + +const defaultProps = { + getPConnect: jest.fn( + () => + ({ + getActionsApi: () => ({ updateFieldValue, triggerFieldChange }), + getStateProps: () => ({ + value: '.dropdown' + }), + getValidationApi: () => ({ + validate + }), + getContextName() { + return 'app/primary_1/workarea_1'; + }, + getCaseInfo() { + return { + getClassName() { + return 'DIXL-MediaCo-Work-NewService'; + } + }; + }, + getDataObject() { + return; + }, + // getCaseInfo: () => ({ getClassName: () => 'Work-' }), + getLocaleRuleNameFromKeys: (className, contextName, ruleName) => `${className}!${contextName}!${ruleName}`, + getLocalizedValue: (value) => value, + updateDirtyCheckChangeList, + clearErrorMessages, + ignoreSuggestion, + acceptSuggestion + }) as any + ), + label: 'Dropdown Label', + required: false, + disabled: false, + value: '', + validatemessage: '', + status: '', + readOnly: false, + testId: 'dropdownTestId', + fieldMetadata: {}, + helperText: '', + displayMode: '', + hideLabel: false, + placeholder: 'Select', + onChange: jest.fn(), + listType: 'associated', + columns: [], + datasource: [ + { key: 'option1', value: 'Option 1' }, + { key: 'option2', value: 'Option 2' }, + { key: 'option3', value: 'Option 3' }, + { key: 'option4', value: 'Option 4' } + ] +}; + +describe('Dropdown Component', () => { + test('renders with required attribute', () => { + const props = { ...defaultProps }; + props.required = true; + const { rerender } = render(); + expect(screen.getAllByText('Dropdown Label')[0]).toHaveClass('Mui-required'); + + props.required = false; + rerender(); + expect(screen.getAllByText('Dropdown Label')[1]).not.toHaveClass('Mui-required'); + }); + + test('renders with disabled attribute', () => { + const props = { ...defaultProps }; + props.disabled = true; + const { rerender } = render(); + expect(screen.getAllByText('Dropdown Label')[0]).toHaveClass('Mui-disabled'); + + props.disabled = false; + rerender(); + expect(screen.getAllByText('Dropdown Label')[1]).not.toHaveAttribute('disabled'); + }); + + test('renders with readOnly attribute', () => { + const props = { ...defaultProps }; + props.readOnly = true; + const { getByTestId, rerender } = render(); + expect(getByTestId('dropdownTestId')).toHaveAttribute('readonly'); + + props.readOnly = false; + rerender(); + expect(getByTestId('dropdownTestId')).not.toHaveAttribute('readonly'); + }); + + test('renders with label', () => { + const props = { ...defaultProps }; + const { getAllByText } = render(); + const labels = getAllByText('Dropdown Label'); + expect(labels.length).toBeGreaterThan(0); + expect(labels[0]).toBeVisible(); + }); + + test('renders in DISPLAY_ONLY mode', () => { + const props = { ...defaultProps }; + props.displayMode = 'DISPLAY_ONLY'; + props.value = 'Option 1'; + const { getByText } = render(); + expect(getByText('Option 1')).toBeVisible(); + }); + + test('renders in STACKED_LARGE_VAL mode', () => { + const props = { ...defaultProps }; + props.displayMode = 'STACKED_LARGE_VAL'; + props.value = 'Option 1'; + const { getByText } = render(); + expect(getByText('Option 1')).toBeVisible(); + }); + + test('does not invoke onBlur handler for readOnly fields', () => { + const props = { ...defaultProps }; + props.readOnly = true; + const { getByTestId } = render(); + fireEvent.change(getByTestId('dropdownTestId'), { target: { value: 'Option 1' } }); + fireEvent.blur(getByTestId('dropdownTestId')); + expect(handleEvent).not.toHaveBeenCalled(); + }); + + test('invokes handlers for blur and change events', () => { + const props = { ...defaultProps }; + const { getByRole, getByText } = render(); + const input = getByRole('combobox'); + fireEvent.mouseDown(input); + fireEvent.click(getByText('Option 1')); + fireEvent.blur(input); + expect(handleEvent).toHaveBeenCalled(); + }); +}); diff --git a/packages/react-sdk-components/tests/unit/components/forms/Email/index.test.tsx b/packages/react-sdk-components/tests/unit/components/forms/Email/index.test.tsx new file mode 100644 index 00000000..b9e30032 --- /dev/null +++ b/packages/react-sdk-components/tests/unit/components/forms/Email/index.test.tsx @@ -0,0 +1,123 @@ +import React from 'react'; +import { render, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; +import Email from '../../../../../src/components/field/Email/index'; +import handleEvent from '../../../../../src/components/helpers/event-utils'; +import TextInput from '../../../../../src/components/field/TextInput'; + +jest.mock('../../../../../src/components/helpers/event-utils'); +jest.mock('../../../../../src/bridge/helpers/sdk_component_map', () => ({ + getComponentFromMap: jest.fn(() => require('../FieldValueList').default) +})); + +const updateFieldValue = jest.fn(); +const triggerFieldChange = jest.fn(); +const updateDirtyCheckChangeList = jest.fn(); +const validate = jest.fn(); +const clearErrorMessages = jest.fn(); +const [ignoreSuggestion, acceptSuggestion] = [jest.fn(), jest.fn()]; + +const defaultProps = { + getPConnect: jest.fn( + () => + ({ + getActionsApi: () => ({ updateFieldValue, triggerFieldChange }), + getStateProps: () => ({ + value: '.email' + }), + getValidationApi: () => ({ + validate + }), + updateDirtyCheckChangeList, + clearErrorMessages, + ignoreSuggestion, + acceptSuggestion + }) as any + ), + label: 'Email Label', + required: false, + disabled: false, + value: '', + validatemessage: '', + status: '', + readOnly: false, + testId: 'emailTestId', + fieldMetadata: {}, + helperText: '', + displayMode: '', + hideLabel: false, + placeholder: '', + onChange: jest.fn() +}; + +describe('Email Component', () => { + test('renders with required attribute', () => { + const props = { ...defaultProps }; + props.required = true; + const { getByTestId } = render(); + expect(getByTestId('emailTestId')).toHaveAttribute('required'); + }); + + test('renders with disabled attribute', () => { + const props = { ...defaultProps }; + props.disabled = true; + const { getByTestId, rerender } = render(); + expect(getByTestId('emailTestId')).toHaveAttribute('disabled'); + + props.disabled = false; + rerender(); + expect(getByTestId('emailTestId')).not.toHaveAttribute('disabled'); + }); + + test('renders with readOnly attribute', () => { + const props = { ...defaultProps }; + props.readOnly = true; + const { getByTestId, rerender } = render(); + expect(getByTestId('emailTestId')).toHaveAttribute('readonly'); + + props.readOnly = false; + rerender(); + expect(getByTestId('emailTestId')).not.toHaveAttribute('readonly'); + }); + + test('renders with label', () => { + const props = { ...defaultProps }; + const { getAllByText } = render(); + const labels = getAllByText('Email Label'); + expect(labels.length).toBeGreaterThan(0); + expect(labels[0]).toBeVisible(); + }); + + test('renders in DISPLAY_ONLY mode', () => { + const props = { ...defaultProps }; + props.displayMode = 'DISPLAY_ONLY'; + props.value = 'test@example.com'; + const { getByText } = render(); + expect(getByText('test@example.com')).toBeVisible(); + }); + + test('renders in STACKED_LARGE_VAL mode', () => { + const props = { ...defaultProps }; + props.displayMode = 'STACKED_LARGE_VAL'; + props.value = 'test@example.com'; + const { getByText } = render(); + expect(getByText('test@example.com')).toBeVisible(); + }); + + test('does not invoke onBlur handler for readOnly fields', () => { + const props = { ...defaultProps }; + props.readOnly = true; + const { getByTestId } = render(); + fireEvent.change(getByTestId('emailTestId'), { target: { value: 'test@example.com' } }); + fireEvent.blur(getByTestId('emailTestId')); + expect(handleEvent).not.toHaveBeenCalled(); + }); + + test('invokes handlers for blur and change events', () => { + const props = { ...defaultProps }; + const { getByTestId } = render(); + fireEvent.change(getByTestId('emailTestId'), { target: { value: 'test@example.com' } }); + fireEvent.blur(getByTestId('emailTestId')); + expect(handleEvent).toHaveBeenCalled(); + }); +}); diff --git a/packages/react-sdk-components/tests/unit/components/forms/FieldValueList.tsx b/packages/react-sdk-components/tests/unit/components/forms/FieldValueList.tsx new file mode 100644 index 00000000..fe658289 --- /dev/null +++ b/packages/react-sdk-components/tests/unit/components/forms/FieldValueList.tsx @@ -0,0 +1,11 @@ +import React from 'react'; + +const FieldValueList = ({ name, value, variant }) => ( +
+ {name} + {value} + {variant} +
+); + +export default FieldValueList; diff --git a/packages/react-sdk-components/tests/unit/components/forms/Integer/index.test.tsx b/packages/react-sdk-components/tests/unit/components/forms/Integer/index.test.tsx new file mode 100644 index 00000000..0da41a3c --- /dev/null +++ b/packages/react-sdk-components/tests/unit/components/forms/Integer/index.test.tsx @@ -0,0 +1,154 @@ +import React from 'react'; +import { render, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; +import Integer from '../../../../../src/components/field/Integer'; +import handleEvent from '../../../../../src/components/helpers/event-utils'; +import TextInput from '../../../../../src/components/field/TextInput'; + +declare global { + interface Window { + PCore: any; + } +} + +window.PCore = { + ...window.PCore, + getEnvironmentInfo: (): any => { + return { + getUseLocale: () => { + return 'en-US'; + }, + getLocale: () => { + return 'en-US'; + } + }; + } +}; + +jest.mock('../../../../../src/components/helpers/event-utils'); +jest.mock('../../../../../src/bridge/helpers/sdk_component_map', () => ({ + getComponentFromMap: jest.fn(() => require('../FieldValueList').default) +})); + +const updateFieldValue = jest.fn(); +const triggerFieldChange = jest.fn(); +const updateDirtyCheckChangeList = jest.fn(); +const validate = jest.fn(); +const clearErrorMessages = jest.fn(); +const [ignoreSuggestion, acceptSuggestion] = [jest.fn(), jest.fn()]; + +const defaultProps = { + getPConnect: jest.fn( + () => + ({ + getActionsApi: () => ({ updateFieldValue, triggerFieldChange }), + getStateProps: () => ({ + value: '.integer' + }), + getValidationApi: () => ({ + validate + }), + getComponentName: () => 'Integer', + updateDirtyCheckChangeList, + clearErrorMessages, + ignoreSuggestion, + acceptSuggestion + }) as any + ), + label: 'Integer Label', + required: false, + disabled: false, + value: '', + validatemessage: '', + status: '', + readOnly: false, + testId: 'integerTestId', + fieldMetadata: {}, + helperText: '', + displayMode: '', + hideLabel: false, + placeholder: '', + onChange: jest.fn() +}; + +describe('Integer Component', () => { + test('renders with required attribute', () => { + const props = { ...defaultProps }; + props.required = true; + const { getByTestId } = render(); + expect(getByTestId('integerTestId')).toHaveAttribute('required'); + }); + + test('renders with disabled attribute', () => { + const props = { ...defaultProps }; + props.disabled = true; + const { getByTestId, rerender } = render(); + expect(getByTestId('integerTestId')).toHaveAttribute('disabled'); + + props.disabled = false; + rerender(); + expect(getByTestId('integerTestId')).not.toHaveAttribute('disabled'); + }); + + test('renders with readOnly attribute', () => { + const props = { ...defaultProps }; + props.readOnly = true; + const { getByTestId, rerender } = render(); + expect(getByTestId('integerTestId')).toHaveAttribute('readonly'); + + props.readOnly = false; + rerender(); + expect(getByTestId('integerTestId')).not.toHaveAttribute('readonly'); + }); + + test('renders with label', () => { + const props = { ...defaultProps }; + const { getAllByText } = render(); + const labels = getAllByText('Integer Label'); + expect(labels.length).toBeGreaterThan(0); + expect(labels[0]).toBeVisible(); + }); + + test('renders in DISPLAY_ONLY mode', () => { + const props = { ...defaultProps }; + props.displayMode = 'DISPLAY_ONLY'; + props.value = '1234'; + const { getByText } = render(); + expect(getByText('1234')).toBeVisible(); + }); + + test('renders in STACKED_LARGE_VAL mode', () => { + const props = { ...defaultProps }; + props.displayMode = 'STACKED_LARGE_VAL'; + props.value = '1234'; + const { getByText } = render(); + expect(getByText('1234')).toBeVisible(); + }); + + test('does not invoke onBlur handler for readOnly fields', () => { + const props = { ...defaultProps }; + props.readOnly = true; + const { getByTestId } = render(); + fireEvent.change(getByTestId('integerTestId'), { target: { value: '1234' } }); + fireEvent.blur(getByTestId('integerTestId')); + expect(handleEvent).not.toHaveBeenCalled(); + }); + + test('invokes handlers for blur and change events', () => { + const props = { ...defaultProps }; + const { getByTestId } = render(); + fireEvent.change(getByTestId('integerTestId'), { target: { value: '1234' } }); + fireEvent.blur(getByTestId('integerTestId')); + expect(handleEvent).toHaveBeenCalled(); + }); + + test('disallows "." and "," characters', () => { + const props = { ...defaultProps }; + const { getByTestId } = render(); + const input = getByTestId('integerTestId') as HTMLInputElement; + fireEvent.change(input, { target: { value: '1234.' } }); + expect(input.value).toBe('1234'); + fireEvent.change(input, { target: { value: '1234,' } }); + expect(input.value).toBe('1234'); + }); +}); diff --git a/packages/react-sdk-components/tests/unit/components/forms/Percentage/index.test.tsx b/packages/react-sdk-components/tests/unit/components/forms/Percentage/index.test.tsx new file mode 100644 index 00000000..cf402c4d --- /dev/null +++ b/packages/react-sdk-components/tests/unit/components/forms/Percentage/index.test.tsx @@ -0,0 +1,160 @@ +import React from 'react'; +import { render, screen, fireEvent, cleanup } from '@testing-library/react'; +import Percentage from '../../../../../src/components/field/Percentage'; + +const mockHandleEvent = jest.fn(); +jest.mock('../../../../../src/components/helpers/event-utils', () => ({ + __esModule: true, + default: (...args) => mockHandleEvent(...args) +})); + +jest.mock('../../../../../src/bridge/helpers/sdk_component_map', () => ({ + getComponentFromMap: jest.fn(() => require('../FieldValueList').default) +})); + +jest.mock('../../../../../src/components/field/Currency/currency-utils', () => ({ + getCurrencyCharacters: jest.fn(() => ({ + theDecimalIndicator: '.', + theDigitGroupSeparator: ',' + })), + getCurrencyOptions: jest.fn(() => ({})) +})); + +jest.mock('../../../../../src/components/helpers/formatters', () => ({ + format: jest.fn((value) => value) +})); + +const updateFieldValue = jest.fn(); +const triggerFieldChange = jest.fn(); +const updateDirtyCheckChangeList = jest.fn(); +const validate = jest.fn(); +const clearErrorMessages = jest.fn(); +const [ignoreSuggestion, acceptSuggestion] = [jest.fn(), jest.fn()]; +const defaultProps = { + getPConnect: jest.fn( + () => + ({ + getActionsApi: () => ({ updateFieldValue, triggerFieldChange }), + getStateProps: () => ({ + value: '.percentage' + }), + getValidationApi: () => ({ + validate + }), + getComponentName: () => 'Percentage', + updateDirtyCheckChangeList, + clearErrorMessages, + ignoreSuggestion, + acceptSuggestion + }) as any + ), + label: 'Percentage Label', + required: false, + disabled: false, + value: '25', + validatemessage: '', + status: '', + readOnly: false, + testId: 'percentageTestId', + helperText: 'Helper text', + displayMode: '', + hideLabel: false, + placeholder: 'Enter percentage', + currencyISOCode: 'USD', + showGroupSeparators: 'true', + decimalPrecision: 2, + onChange: jest.fn() +}; + +describe('Percentage Component', () => { + afterEach(() => { + cleanup(); + jest.clearAllMocks(); + }); + + test('renders with label and placeholder', () => { + render(); + expect(screen.getByLabelText('Percentage Label')).toBeInTheDocument(); + expect(screen.getByPlaceholderText('Enter percentage')).toBeInTheDocument(); + }); + + test('renders with required attribute', () => { + const props = { ...defaultProps, required: true }; + const { rerender } = render(); + const input = screen.getByTestId('percentageTestId'); + expect(input).toBeRequired(); + + props.required = false; + rerender(); + expect(input).not.toBeRequired(); + }); + + test('renders with disabled attribute', () => { + const props = { ...defaultProps, disabled: true }; + const { rerender } = render(); + const input = screen.getByTestId('percentageTestId'); + expect(input).toBeDisabled(); + + props.disabled = false; + rerender(); + expect(input).not.toBeDisabled(); + }); + + test('renders with readOnly attribute', () => { + const props = { ...defaultProps, readOnly: true }; + const { rerender, getByTestId } = render(); + const input = screen.getByLabelText('Percentage Label'); + expect(input).toHaveAttribute('readonly'); + + props.readOnly = false; + rerender(); + expect(getByTestId('percentageTestId')).not.toHaveAttribute('readonly'); + }); + + test('renders with helper text', () => { + render(); + expect(screen.getByText('Helper text')).toBeInTheDocument(); + }); + + test('renders with validation message overriding helper text', () => { + const props = { ...defaultProps, validatemessage: 'Validation error' }; + render(); + expect(screen.getByText('Validation error')).toBeInTheDocument(); + expect(screen.queryByText('Helper text')).not.toBeInTheDocument(); + }); + + test('updates value on change', () => { + render(); + const input = screen.getByLabelText('Percentage Label'); + fireEvent.change(input, { target: { value: '50' } }); + expect(input).toHaveValue('50%'); + }); + + test('calls handleEvent on blur when not readOnly', () => { + render(); + const input = screen.getByLabelText('Percentage Label'); + fireEvent.change(input, { target: { value: '75' } }); + fireEvent.blur(input); + expect(mockHandleEvent).toHaveBeenCalledWith(expect.any(Object), 'changeNblur', '.percentage', '75'); + }); + + test('does not call handleEvent on blur when readOnly', () => { + const props = { ...defaultProps, readOnly: true }; + render(); + const input = screen.getByLabelText('Percentage Label'); + fireEvent.blur(input); + expect(mockHandleEvent).not.toHaveBeenCalled(); + }); + + test('renders in DISPLAY_ONLY mode', () => { + const props = { ...defaultProps, displayMode: 'DISPLAY_ONLY', value: '30' }; + render(); + expect(screen.getByText('30')).toBeInTheDocument(); + }); + + test('renders in STACKED_LARGE_VAL mode', () => { + const props = { ...defaultProps, displayMode: 'STACKED_LARGE_VAL', value: '45' }; + render(); + expect(screen.getByText('45')).toBeInTheDocument(); + }); +}); diff --git a/packages/react-sdk-components/tests/unit/components/forms/RadioButtons/index.test.tsx b/packages/react-sdk-components/tests/unit/components/forms/RadioButtons/index.test.tsx new file mode 100644 index 00000000..01138249 --- /dev/null +++ b/packages/react-sdk-components/tests/unit/components/forms/RadioButtons/index.test.tsx @@ -0,0 +1,138 @@ +import React from 'react'; +import { render, fireEvent, screen } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; +import RadioButtons from '../../../../../src/components/field/RadioButtons'; + +const mockHandleEvent = jest.fn(); +jest.mock('../../../../../src/components/helpers/event-utils', () => ({ + __esModule: true, + default: (...args) => mockHandleEvent(...args) +})); + +jest.mock('../../../../../src/components/helpers/event-utils'); +jest.mock('../../../../../src/bridge/helpers/sdk_component_map', () => ({ + getComponentFromMap: jest.fn(() => require('../FieldValueList').default) +})); +jest.mock('../../../../../src/components/helpers/utils', () => ({ + getOptionList: jest.fn(() => [ + { key: 'option1', value: 'Option 1' }, + { key: 'option2', value: 'Option 2' } + ]) +})); + +const updateFieldValue = jest.fn(); +const triggerFieldChange = jest.fn(); +const validate = jest.fn(); +let rawMetaData = { + config: { + value: '@P .Radiobuttonpick' + }, + type: 'RadioButtons' +}; +const defaultProps = { + getPConnect: jest.fn( + () => + ({ + getActionsApi: () => ({ updateFieldValue, triggerFieldChange }), + getStateProps: () => ({ + value: '.radio' + }), + getRawMetadata: jest.fn(() => rawMetaData), + getValidationApi: () => ({ + validate + }), + getConfigProps: jest.fn(() => ({})), + getDataObject: jest.fn(() => ({})), + getCaseInfo: jest.fn(() => ({ + getClassName: jest.fn(() => 'TestClass') + })), + getLocalizedValue: jest.fn((value) => value), + getLocaleRuleNameFromKeys: jest.fn(() => 'localeRuleName') + }) as any + ), + label: 'Radio Label', + required: false, + disabled: false, + value: '', + validatemessage: '', + status: '', + readOnly: false, + testId: 'radioTestId', + fieldMetadata: {}, + helperText: '', + displayMode: '', + hideLabel: false, + placeholder: '', + inline: false, + onChange: jest.fn() +}; + +describe('RadioButtons Component', () => { + test('renders with required attribute', () => { + const props = { ...defaultProps }; + props.required = true; + render(); + expect(screen.getByText('Radio Label')).toBeVisible(); + expect(screen.getByText('Radio Label').closest('legend')).toHaveClass('Mui-required'); + }); + + test('renders with readOnly attribute', () => { + const props = { ...defaultProps }; + props.readOnly = true; + render(); + expect(screen.getByLabelText('Option 1')).toBeDisabled(); + expect(screen.getByLabelText('Option 2')).toBeDisabled(); + }); + + test('renders with disabled attribute', () => { + const props = { ...defaultProps }; + props.disabled = true; + render(); + expect(screen.getByLabelText('Option 1')).toBeDisabled(); + expect(screen.getByLabelText('Option 2')).toBeDisabled(); + }); + + test('renders with label', () => { + const props = { ...defaultProps }; + render(); + expect(screen.getByText('Radio Label')).toBeVisible(); + }); + + test('renders in DISPLAY_ONLY mode', () => { + const props = { ...defaultProps }; + props.displayMode = 'DISPLAY_ONLY'; + props.value = 'Option 1'; + render(); + expect(screen.getByText('Option 1')).toBeVisible(); + }); + + test('renders in STACKED_LARGE_VAL mode', () => { + const props = { ...defaultProps }; + props.displayMode = 'STACKED_LARGE_VAL'; + props.value = 'Option 1'; + render(); + expect(screen.getByText('Option 1')).toBeVisible(); + }); + + test('does not invoke onBlur handler for readOnly fields', () => { + const props = { ...defaultProps }; + props.readOnly = true; + render(); + fireEvent.change(screen.getByLabelText('Option 1'), { target: { value: 'Option 1' } }); + fireEvent.blur(screen.getByLabelText('Option 1')); + expect(mockHandleEvent).not.toHaveBeenCalled(); + }); + + test('invokes handlers for blur and change events', () => { + const props = { ...defaultProps }; + render(); + const radioButtons = screen.getByLabelText('Option 1') as HTMLInputElement; + fireEvent.click(radioButtons, { + target: { + value: 'Option 1' + } + }); + expect(radioButtons.value).toBe('option1'); + expect(mockHandleEvent).toHaveBeenCalled(); + }); +}); diff --git a/packages/react-sdk-components/tests/unit/components/forms/TextArea/index.test.tsx b/packages/react-sdk-components/tests/unit/components/forms/TextArea/index.test.tsx new file mode 100644 index 00000000..5a94d68b --- /dev/null +++ b/packages/react-sdk-components/tests/unit/components/forms/TextArea/index.test.tsx @@ -0,0 +1,127 @@ +import React from 'react'; +import { render, fireEvent } from '@testing-library/react'; +import TextArea from '../../../../../src/components/field/TextArea'; + +const mockHandleEvent = jest.fn(); +jest.mock('../../../../../src/components/helpers/event-utils', () => ({ + __esModule: true, + default: (...args) => mockHandleEvent(...args) +})); + +jest.mock('../../../../../src/components/helpers/event-utils'); + +jest.mock('../../../../../src/bridge/helpers/sdk_component_map', () => ({ + getComponentFromMap: jest.fn(() => require('../FieldValueList').default) +})); + +const updateFieldValue = jest.fn(); +const triggerFieldChange = jest.fn(); +const updateDirtyCheckChangeList = jest.fn(); +const validate = jest.fn(); +const clearErrorMessages = jest.fn(); +const [ignoreSuggestion, acceptSuggestion] = [jest.fn(), jest.fn()]; +const defaultProps = { + getPConnect: jest.fn( + () => + ({ + getActionsApi: () => ({ updateFieldValue, triggerFieldChange }), + getStateProps: () => ({ + value: '.textArea' + }), + getValidationApi: () => ({ + validate + }), + updateDirtyCheckChangeList, + clearErrorMessages, + ignoreSuggestion, + acceptSuggestion + }) as any + ), + label: 'TextArea Label', + required: true, + disabled: false, + value: '', + validatemessage: '', + status: '', + readOnly: false, + testId: 'textAreaTestId', + fieldMetadata: {}, + helperText: '', + displayMode: '', + hideLabel: false, + placeholder: '', + onChange: jest.fn() +}; + +describe('TextArea Component', () => { + test('renders with required attribute', () => { + const props = { ...defaultProps }; + const { getByTestId, rerender } = render(