From 1e5a3b2f975010e1e5ed7bb86e22e95397fd2117 Mon Sep 17 00:00:00 2001 From: manasa Date: Wed, 15 Oct 2025 16:39:06 +0530 Subject: [PATCH 1/4] Added unit Tests for form field components --- jest.config.js | 6 +- package.json | 2 +- .../components/field/Checkbox/Checkbox.tsx | 14 +- .../components/field/DateTime/DateTime.tsx | 4 +- .../field/RadioButtons/RadioButtons.tsx | 13 +- .../forms/Autocomplete/index.test.tsx | 340 ++++++++++++++ .../components/forms/Checkbox/index.test.tsx | 437 ++++++++++++++++++ .../components/forms/Currency/index.test.tsx | 147 ++++++ .../unit/components/forms/Date/index.test.tsx | 157 +++++++ .../components/forms/DateTime/index.test.tsx | 160 +++++++ .../components/forms/Decimal/index.test.tsx | 146 ++++++ .../components/forms/Dropdown/index.test.tsx | 159 +++++++ .../components/forms/Email/index.test.tsx | 123 +++++ .../unit/components/forms/FieldValueList.tsx | 11 + .../components/forms/Integer/index.test.tsx | 154 ++++++ .../forms/Percentage/index.test.tsx | 304 ++++++++++++ .../components/forms/Phone/index.test.tsx | 288 ++++++++++++ .../forms/RadioButtons/index.test.tsx | 138 ++++++ .../components/forms/TextArea/index.test.tsx | 127 +++++ .../components/forms/TextInput/index.test.tsx | 189 +++++--- .../unit/components/forms/Time/index.test.tsx | 171 +++++++ .../unit/components/forms/URL/index.test.tsx | 131 ++++++ .../tests/unit/styleMock.js | 1 + sdk-config.json | 2 +- tsconfig.json | 4 +- 25 files changed, 3147 insertions(+), 81 deletions(-) create mode 100644 packages/react-sdk-components/tests/unit/components/forms/Autocomplete/index.test.tsx create mode 100644 packages/react-sdk-components/tests/unit/components/forms/Checkbox/index.test.tsx create mode 100644 packages/react-sdk-components/tests/unit/components/forms/Currency/index.test.tsx create mode 100644 packages/react-sdk-components/tests/unit/components/forms/Date/index.test.tsx create mode 100644 packages/react-sdk-components/tests/unit/components/forms/DateTime/index.test.tsx create mode 100644 packages/react-sdk-components/tests/unit/components/forms/Decimal/index.test.tsx create mode 100644 packages/react-sdk-components/tests/unit/components/forms/Dropdown/index.test.tsx create mode 100644 packages/react-sdk-components/tests/unit/components/forms/Email/index.test.tsx create mode 100644 packages/react-sdk-components/tests/unit/components/forms/FieldValueList.tsx create mode 100644 packages/react-sdk-components/tests/unit/components/forms/Integer/index.test.tsx create mode 100644 packages/react-sdk-components/tests/unit/components/forms/Percentage/index.test.tsx create mode 100644 packages/react-sdk-components/tests/unit/components/forms/Phone/index.test.tsx create mode 100644 packages/react-sdk-components/tests/unit/components/forms/RadioButtons/index.test.tsx create mode 100644 packages/react-sdk-components/tests/unit/components/forms/TextArea/index.test.tsx create mode 100644 packages/react-sdk-components/tests/unit/components/forms/Time/index.test.tsx create mode 100644 packages/react-sdk-components/tests/unit/components/forms/URL/index.test.tsx create mode 100644 packages/react-sdk-components/tests/unit/styleMock.js 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/Checkbox/Checkbox.tsx b/packages/react-sdk-components/src/components/field/Checkbox/Checkbox.tsx index 4306370c..b716f07a 100644 --- a/packages/react-sdk-components/src/components/field/Checkbox/Checkbox.tsx +++ b/packages/react-sdk-components/src/components/field/Checkbox/Checkbox.tsx @@ -116,11 +116,11 @@ export default function CheckboxComponent(props: CheckboxProps) { return ; } - const handleChange = (event) => { + const handleChange = event => { handleEvent(actionsApi, 'changeNblur', propName, event.target.checked); }; - const handleBlur = (event) => { + const handleBlur = event => { thePConn.getValidationApi().validate(event.target.checked); }; @@ -155,10 +155,10 @@ export default function CheckboxComponent(props: CheckboxProps) { dataSource={datasource} getPConnect={getPConnect} readOnly={renderMode === 'ReadOnly' || displayMode === 'DISPLAY_ONLY' || readOnly} - onChange={(e) => { + onChange={e => { e.stopPropagation(); const recordKey = selectionKey?.split('.').pop(); - const selectedItem = datasource?.source?.find((item) => item[recordKey as any] === e.target.id) ?? {}; + const selectedItem = datasource?.source?.find(item => item[recordKey as any] === e.target.id) ?? {}; handleCheckboxChange(e, { id: selectedItem[recordKey as any], primary: selectedItem[recordKey as any] @@ -216,12 +216,12 @@ export default function CheckboxComponent(props: CheckboxProps) { control={ data[dataField] === element.key)} - onChange={(event) => handleChangeMultiMode(event, element)} + checked={selectedvalues?.some?.(data => data[dataField] === element.key)} + onChange={event => handleChangeMultiMode(event, element)} 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..0f0f720e 100644 --- a/packages/react-sdk-components/src/components/field/DateTime/DateTime.tsx +++ b/packages/react-sdk-components/src/components/field/DateTime/DateTime.tsx @@ -62,8 +62,8 @@ 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 handleChange = date => { + 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..0ef84b62 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); @@ -59,7 +60,7 @@ export default function RadioButtons(props: RadioButtonsProps) { configProperty = configProperty.startsWith('@P') ? configProperty.substring(3) : configProperty; configProperty = configProperty.startsWith('.') ? configProperty.substring(1) : configProperty; - const metaData = Array.isArray(fieldMetadata) ? fieldMetadata.filter((field) => field?.classID === className)[0] : fieldMetadata; + const metaData = Array.isArray(fieldMetadata) ? fieldMetadata.filter(field => field?.classID === className)[0] : fieldMetadata; let displayName = metaData?.datasource?.propertyForDisplayText; displayName = displayName?.slice(displayName.lastIndexOf('.') + 1); const localeContext = metaData?.datasource?.tableType === 'DataPage' ? 'datapage' : 'associated'; @@ -95,11 +96,11 @@ export default function RadioButtons(props: RadioButtonsProps) { ); } - const handleChange = (event) => { + const handleChange = event => { handleEvent(actionsApi, 'changeNblur', propName, event.target.value); }; - const handleBlur = (event) => { + const handleBlur = event => { thePConn.getValidationApi().validate(event.target.value, ''); // 2nd arg empty string until typedef marked correctly as optional }; @@ -137,7 +138,7 @@ export default function RadioButtons(props: RadioButtonsProps) { {label} - {theOptions.map((theOption) => { + {theOptions.map(theOption => { return ( } + 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..3601a1cc --- /dev/null +++ b/packages/react-sdk-components/tests/unit/components/forms/Autocomplete/index.test.tsx @@ -0,0 +1,340 @@ +// 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 getDefaultProps = () => ({ +// 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 = getDefaultProps(); +// 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 = getDefaultProps(); +// 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 = getDefaultProps(); +// 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 = getDefaultProps(); +// const { getByText } = render(); +// expect(getByText('AutoComplete')).toBeVisible(); +// }); + +// test('renders in DISPLAY_ONLY mode', () => { +// const props = getDefaultProps(); +// 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 = getDefaultProps(); +// 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 = getDefaultProps(); +// 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 = getDefaultProps(); +// 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 = getDefaultProps(); +// 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 = getDefaultProps(); +// const { getByRole, getByText } = render(); +// const input = getByRole('combobox'); +// fireEvent.change(input, { target: { value: 'Option 1' } }); +// expect(getByText('Option 1')).toBeVisible(); +// }); +// }); + +import React from 'react'; +import { render, fireEvent, screen, cleanup, waitFor } from '@testing-library/react'; +import AutoComplete from '../../../../../src/components/field/AutoComplete'; + +const handleEvent = jest.fn(); +const getDataPage = jest.fn(() => + Promise.resolve([ + { pyGUID: '1', name: 'Option 1' }, + { pyGUID: '2', name: 'Option 2' } + ]) +); + +jest.mock('../../../../../src/components/helpers/event-utils', () => ({ + __esModule: true, + default: (...args) => handleEvent(...args) +})); + +jest.mock('../../../../../src/components/helpers/data_page', () => ({ + __esModule: true, + getDataPage: (...args) => getDataPage() +})); + +jest.mock('../../../../../src/components/helpers/utils', () => ({ + __esModule: true, + default: { + getOptionList: () => [ + { key: '1', value: 'Associated Option 1' }, + { key: '2', value: 'Associated Option 2' } + ] + } +})); + +// jest.mock('../../../../../src/bridge/helpers/sdk_component_map', () => ({ +// __esModule: true, +// getComponentFromMap: name => { +// if (name === 'TextInput') { +// return props => ; +// } +// if (name === 'FieldValueList') { +// return ({ name, value, variant }) =>
{`${name}: ${value}`}
; +// } +// return null; +// } +// })); + +jest.mock('../../../../../src/bridge/helpers/sdk_component_map', () => ({ + getComponentFromMap: jest.fn(() => require('../FieldValueList').default) +})); + +const getPConnect = jest.fn( + () => + ({ + getContextName: () => 'appContext', + getActionsApi: () => ({}), + getStateProps: () => ({ value: '.autoComplete' }), + getDataObject: () => ({}) + }) as any +); + +const getDefaultProps = () => ({ + getPConnect, + label: 'AutoComplete Label', + required: false, + placeholder: 'Select option', + value: '1', + validatemessage: '', + readOnly: false, + testId: 'autoCompleteTestId', + displayMode: '', + deferDatasource: false, + datasourceMetadata: null, + status: '', + helperText: 'AutoComplete helper text', + hideLabel: false, + onRecordChange: jest.fn(), + listType: 'associated', + parameters: {}, + datasource: [{ key: '1', value: 'Associated Option 1' }], + columns: [ + { key: 'true', value: 'key' }, + { display: 'true', primary: 'true', value: 'value' } + ], + onChange: jest.fn(), + disabled: false +}); + +describe('AutoComplete Component', () => { + afterEach(() => { + cleanup(); + jest.clearAllMocks(); + }); + + test('renders with label and helper text', () => { + const props = getDefaultProps(); + props.required = false; + render(); + expect(screen.getByText('AutoComplete helper text')).toBeInTheDocument(); + expect(screen.getByLabelText('AutoComplete Label')).toBeVisible(); + }); + + test('renders with required attribute', () => { + const props = getDefaultProps(); + render(); + const input = screen.getByLabelText('AutoComplete Label'); + expect(input).toBeRequired(); + }); + + test('renders with validation message overriding helper text', () => { + const props = getDefaultProps(); + props.validatemessage = 'Validation error'; + render(); + expect(screen.getByText('Validation error')).toBeVisible(); + expect(screen.queryByText('AutoComplete helper text')).not.toBeInTheDocument(); + }); + + test('calls handleEvent on selection change', () => { + const props = getDefaultProps(); + render(); + const input = screen.getByLabelText('AutoComplete Label'); + fireEvent.change(input, { target: { value: 'Associated Option 1' } }); + fireEvent.blur(input); + expect(handleEvent).toHaveBeenCalledWith(expect.any(Object), 'changeNblur', '.autoComplete', ''); + }); + + test('renders in DISPLAY_ONLY mode', () => { + const props = getDefaultProps(); + props.displayMode = 'DISPLAY_ONLY'; + render(); + expect(screen.getByTestId('field-value-default')).toHaveTextContent('AutoComplete Label: 1'); + }); + + test('renders in STACKED_LARGE_VAL mode', () => { + const props = getDefaultProps(); + props.displayMode = 'STACKED_LARGE_VAL'; + render(); + expect(screen.getByTestId('field-value-stacked')).toHaveTextContent('AutoComplete Label: 1'); + }); + + test('renders in readOnly mode with mapped value', () => { + const props = getDefaultProps(); + props.readOnly = true; + render(); + expect(screen.getByTestId('text-input')).toHaveAttribute('value', 'Associated Option 1'); + }); + + test('renders with placeholder', () => { + const props = getDefaultProps(); + render(); + expect(screen.getByPlaceholderText('Select option')).toBeInTheDocument(); + }); + + test('loads options when listType is "associated"', async () => { + const props = getDefaultProps(); + props.listType = 'associated'; + render(); + await waitFor(() => { + expect(screen.getByLabelText('AutoComplete Label')).toBeInTheDocument(); + }); + expect(screen.getByLabelText('AutoComplete Label')).toHaveValue('Associated Option 1'); + }); + + // test('loads options when listType is not "associated"', async () => { + // const props = getDefaultProps(); + // props.listType = 'datapage'; + // props.datasource = 'D_TestPage'; + // props.parameters = {}; + // props.columns = [ + // { key: 'true', value: 'pyGUID' }, + // { display: 'true', primary: 'true', value: 'name' } + // ]; + // render(); + // await waitFor(() => { + // expect(getDataPage).toHaveBeenCalledWith('D_TestPage', {}, 'appContext'); + // }); + // expect(screen.getByLabelText('AutoComplete Label')).toBeInTheDocument(); + // }); +}); 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..94db1ffa --- /dev/null +++ b/packages/react-sdk-components/tests/unit/components/forms/Checkbox/index.test.tsx @@ -0,0 +1,437 @@ +// import React from 'react'; +// import { render, fireEvent, screen } from '@testing-library/react'; +// import '@testing-library/jest-dom/extend-expect'; +// import CheckboxComponent from '../../../../../src/components/field/Checkbox'; +// import handleEvent from '../../../../../src/components/helpers/event-utils'; + +// import { insertInstruction, deleteInstruction, updateNewInstuctions } from '../../../../../src/components/helpers/instructions-utils'; + +// 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/instructions-utils', () => ({ +// insertInstruction: jest.fn(), +// deleteInstruction: jest.fn(), +// updateNewInstuctions: jest.fn() +// })); + +// const updateFieldValue = jest.fn(); +// const triggerFieldChange = jest.fn(); +// const validate = jest.fn(); +// const clearErrorMessages = jest.fn(); + +// const getDefaultProps = () => ({ +// getPConnect: jest.fn( +// () => +// ({ +// getActionsApi: () => ({ updateFieldValue, triggerFieldChange }), +// getStateProps: () => ({ +// value: '.checkbox' +// }), +// getValidationApi: () => ({ +// validate +// }), +// clearErrorMessages +// }) as any +// ), +// label: 'Checkbox Label', +// caption: 'Checkbox Caption', +// required: false, +// disabled: false, +// value: false, +// validatemessage: '', +// status: '', +// readOnly: false, +// testId: 'checkboxTestId', +// fieldMetadata: {}, +// helperText: '', +// displayMode: '', +// hideLabel: false, +// trueLabel: 'True', +// falseLabel: 'False', +// selectionMode: '', +// datasource: {}, +// selectionKey: '', +// selectionList: '', +// primaryField: '', +// referenceList: '', +// readonlyContextList: [{ key: '' }], +// onChange: jest.fn() +// }); + +// describe('CheckboxComponent', () => { +// test('renders with required attribute', () => { +// const props = getDefaultProps(); +// 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 = getDefaultProps(); +// props.disabled = true; +// render(); +// expect(screen.getByLabelText('Checkbox Caption')).toBeDisabled(); +// }); + +// test('renders with readOnly attribute', () => { +// const props = getDefaultProps(); +// props.readOnly = true; +// render(); +// expect(screen.getByLabelText('Checkbox Caption')).toHaveAttribute('readonly'); +// }); + +// test('renders with label', () => { +// const props = getDefaultProps(); +// render(); +// expect(screen.getByText('Checkbox Label')).toBeVisible(); +// }); + +// test('renders in DISPLAY_ONLY mode', () => { +// const props = getDefaultProps(); +// props.displayMode = 'DISPLAY_ONLY'; +// props.value = true; +// render(); +// expect(screen.getByText('True')).toBeVisible(); +// }); + +// test('renders in STACKED_LARGE_VAL mode', () => { +// const props = getDefaultProps(); +// props.displayMode = 'STACKED_LARGE_VAL'; +// props.value = true; +// render(); +// expect(screen.getByText('True')).toBeVisible(); +// }); + +// test('does not invoke onBlur handler for readOnly fields', () => { +// const props = getDefaultProps(); +// props.readOnly = true; +// render(); +// fireEvent.change(screen.getByTestId('checkboxTestId'), { target: { checked: true } }); +// fireEvent.blur(screen.getByTestId('checkboxTestId')); +// expect(handleEvent).not.toHaveBeenCalled(); +// }); + +// test('invokes handlers for blur and change events', () => { +// const props = getDefaultProps(); +// render(); + +// const checkbox = screen.getByLabelText('Checkbox Caption') as HTMLInputElement; +// fireEvent.click(checkbox, { +// target: { +// checked: true +// } +// }); +// expect(handleEvent).toHaveBeenCalled(); +// }); + +// test('renders multiple checkboxes in multi-selection mode', () => { +// const props = getDefaultProps(); +// props.selectionMode = 'multi'; +// props.datasource = { +// source: [ +// { key: '1', value: 'Option 1' }, +// { key: '2', value: 'Option 2' }, +// { key: '3', value: 'Option 3' }, +// { key: '4', value: 'Option 4' } +// ] +// }; +// props.selectionKey = 'selection.key'; +// props.selectionList = 'selectionList'; +// props.primaryField = 'primaryField'; +// props.readonlyContextList = [{ key: '1' }]; +// const { getByLabelText } = render(); +// expect(getByLabelText('Option 1').closest('span')).toHaveClass('Mui-checked'); +// }); + +// test('invokes handlers for multi-selection mode', () => { +// const props = getDefaultProps(); +// props.selectionMode = 'multi'; +// props.datasource = { +// source: [ +// { key: '1', value: 'Option 1' }, +// { key: '2', value: 'Option 2' } +// ] +// }; +// props.selectionKey = 'selection.key'; +// props.selectionList = 'selectionList'; +// props.primaryField = 'primaryField'; +// props.readonlyContextList = [{ key: '1' }]; +// render(); +// const checkbox1 = screen.getByLabelText('Option 1') as HTMLInputElement; +// fireEvent.click(checkbox1, { +// target: { +// checked: false +// } +// }); +// const checkbox2 = screen.getByLabelText('Option 1') as HTMLInputElement; +// fireEvent.click(checkbox2, { +// target: { +// checked: true +// } +// }); +// expect(insertInstruction).toHaveBeenCalled(); +// }); +// }); + +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; + } + // if (name === 'FieldValueList') { + // return ({ name, value, variant }) =>
{`${name}: ${value}`}
; + // } + return null; + } +})); + +const getPConnect = jest.fn( + () => + ({ + getActionsApi: () => ({}), + getStateProps: () => ({ value: '.checkbox' }), + getValidationApi: () => ({ validate }), + clearErrorMessages, + setReferenceList: jest.fn(), + getRawMetadata: () => ({ config: { imageDescription: 'data.description' } }) + }) as any +); + +const getDefaultProps = () => ({ + 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 = getDefaultProps(); + render(); + expect(screen.getByText('Checkbox Label')).toBeInTheDocument(); + expect(screen.getByText('Checkbox helper text')).toBeInTheDocument(); + }); + + test('renders with required attribute', () => { + const props = getDefaultProps(); + 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 = getDefaultProps(); + props.required = true; + props.disabled = true; + render(); + const checkbox = screen.getByRole('checkbox'); + expect(checkbox).toBeDisabled(); + }); + + test('renders with readOnly attribute', () => { + const props = getDefaultProps(); + props.readOnly = true; + render(); + const checkbox = screen.getByRole('checkbox'); + expect(checkbox).toHaveAttribute('readonly'); + }); + + test('renders with validation message overriding helper text', () => { + const props = getDefaultProps(); + 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 = getDefaultProps(); + 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 = getDefaultProps(); + render(); + const checkbox = screen.getByRole('checkbox'); + fireEvent.blur(checkbox); + expect(validate).toHaveBeenCalledWith(true); + }); + + test('does not call handleEvent or validate when readOnly', () => { + const props = getDefaultProps(); + 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 = getDefaultProps(); + 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 = getDefaultProps(); + 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 = getDefaultProps(); + 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 = getDefaultProps(); + props.variant = 'card'; + render(); + expect(screen.getByTestId(`selectable-card-${props.testId}`)).toBeInTheDocument(); + }); + + test('handles card option selection', () => { + const props = getDefaultProps(); + 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 = getDefaultProps(); + 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 = getDefaultProps(); + 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..6f94794a --- /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..425ed3ba --- /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..79666646 --- /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..ac168de1 --- /dev/null +++ b/packages/react-sdk-components/tests/unit/components/forms/Percentage/index.test.tsx @@ -0,0 +1,304 @@ +// import React from 'react'; +// import { render, fireEvent } from '@testing-library/react'; +// import '@testing-library/jest-dom/extend-expect'; +// import Percentage from '../../../../../src/components/field/Percentage'; +// 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 getDefaultProps = () => ({ +// 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: '', +// validatemessage: '', +// status: '', +// readOnly: false, +// testId: 'percentageTestId', +// fieldMetadata: {}, +// helperText: '', +// displayMode: '', +// hideLabel: false, +// placeholder: '', +// onChange: jest.fn() +// }); + +// describe('Percentage Component', () => { +// test('renders with required attribute', () => { +// const props = getDefaultProps(); +// props.required = true; +// const { getByTestId } = render(); +// expect(getByTestId('percentageTestId')).toHaveAttribute('required'); +// }); + +// test('renders with disabled attribute', () => { +// const props = getDefaultProps(); +// props.disabled = true; +// const { getByTestId, rerender } = render(); +// expect(getByTestId('percentageTestId')).toHaveAttribute('disabled'); + +// props.disabled = false; +// rerender(); +// expect(getByTestId('percentageTestId')).not.toHaveAttribute('disabled'); +// }); + +// test('renders with readOnly attribute', () => { +// const props = getDefaultProps(); +// props.readOnly = true; +// const { getByTestId, rerender } = render(); +// expect(getByTestId('percentageTestId')).toHaveAttribute('readonly'); + +// props.readOnly = false; +// rerender(); +// expect(getByTestId('percentageTestId')).not.toHaveAttribute('readonly'); +// }); + +// test('renders with label', () => { +// const props = getDefaultProps(); +// const { getAllByText } = render(); +// const labels = getAllByText('Percentage Label'); +// expect(labels.length).toBeGreaterThan(0); +// expect(labels[0]).toBeVisible(); +// }); + +// test('renders in DISPLAY_ONLY mode', () => { +// const props = getDefaultProps(); +// props.displayMode = 'DISPLAY_ONLY'; +// props.value = '50'; +// const { getByText } = render(); +// expect(getByText('50.00%')).toBeVisible(); +// }); + +// test('renders in STACKED_LARGE_VAL mode', () => { +// const props = getDefaultProps(); +// props.displayMode = 'STACKED_LARGE_VAL'; +// props.value = '50'; +// const { getByText } = render(); +// expect(getByText('50.00%')).toBeVisible(); +// }); + +// test('does not invoke onBlur handler for readOnly fields', () => { +// const props = getDefaultProps(); +// props.readOnly = true; +// const { getByTestId } = render(); +// fireEvent.change(getByTestId('percentageTestId'), { target: { value: '50' } }); +// fireEvent.blur(getByTestId('percentageTestId')); +// expect(handleEvent).not.toHaveBeenCalled(); +// }); + +// test('invokes handlers for blur and change events', () => { +// const props = getDefaultProps(); +// const { getByTestId } = render(); +// fireEvent.change(getByTestId('percentageTestId'), { target: { value: '50' } }); +// fireEvent.blur(getByTestId('percentageTestId')); +// expect(handleEvent).toHaveBeenCalled(); +// }); +// }); + +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/Phone/index.test.tsx b/packages/react-sdk-components/tests/unit/components/forms/Phone/index.test.tsx new file mode 100644 index 00000000..22ed550c --- /dev/null +++ b/packages/react-sdk-components/tests/unit/components/forms/Phone/index.test.tsx @@ -0,0 +1,288 @@ +// import React from 'react'; +// import { render, fireEvent, screen } from '@testing-library/react'; +// import '@testing-library/jest-dom/extend-expect'; +// import Phone from '../../../../../src/components/field/Phone'; +// import handleEvent from '../../../../../src/components/helpers/event-utils'; + +// 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 getDefaultProps = () => ({ +// getPConnect: jest.fn( +// () => +// ({ +// getActionsApi: () => ({ updateFieldValue, triggerFieldChange }), +// getStateProps: () => ({ +// value: '.phone' +// }), +// getValidationApi: () => ({ +// validate +// }), +// updateDirtyCheckChangeList, +// clearErrorMessages, +// ignoreSuggestion, +// acceptSuggestion +// }) as any +// ), +// label: 'Phone Label', +// required: false, +// disabled: false, +// value: '', +// validatemessage: '', +// status: '', +// readOnly: false, +// testId: 'phoneTestId', +// fieldMetadata: {}, +// helperText: '', +// displayMode: '', +// hideLabel: false, +// placeholder: 'phone number', +// onChange: jest.fn() +// }); +// // const testIds = Phone.getTestIds(getDefaultProps().testId); +// // Removed the call to getTestIds as it does not exist on MuiPhoneNumber + +// describe('Phone Component', () => { +// test('renders with required attribute', () => { +// const props = getDefaultProps(); +// props.required = true; +// const { rerender } = render(); +// expect(screen.getByPlaceholderText('phone number')).toHaveAttribute('required'); + +// props.required = false; +// rerender(); +// expect(screen.getByPlaceholderText('phone number')).not.toHaveAttribute('required'); +// }); + +// // test('renders with disabled attribute', () => { +// // const props = getDefaultProps(); +// // props.disabled = true; +// // const { getByTestId, rerender } = render(); +// // expect(getByTestId('phoneTestId')).toHaveAttribute('disabled'); + +// // props.disabled = false; +// // rerender(); +// // expect(getByTestId('phoneTestId')).not.toHaveAttribute('disabled'); +// // }); + +// test('renders with disabled attribute', () => { +// const props = getDefaultProps(); +// props.disabled = true; +// const { rerender } = render(); +// expect(screen.getByPlaceholderText('phone number')).toHaveAttribute('disabled'); + +// props.disabled = false; +// rerender(); +// expect(screen.getByPlaceholderText('phone number')).not.toHaveAttribute('disabled'); +// }); + +// test('renders with readOnly attribute', () => { +// const props = getDefaultProps(); +// props.readOnly = true; +// const { getByTestId, rerender } = render(); +// expect(getByTestId('phoneTestId')).toHaveAttribute('readonly'); + +// props.readOnly = false; +// rerender(); +// expect(getByTestId('phoneTestId')).not.toHaveAttribute('readonly'); +// }); + +// test('renders with label', () => { +// const props = getDefaultProps(); +// const { getAllByText } = render(); +// const labels = getAllByText('Phone Label'); +// expect(labels.length).toBeGreaterThan(0); +// expect(labels[0]).toBeVisible(); +// }); + +// test('renders in DISPLAY_ONLY mode', () => { +// const props = getDefaultProps(); +// props.displayMode = 'DISPLAY_ONLY'; +// props.value = '+1234567890'; +// const { getByText } = render(); +// expect(getByText('+1234567890')).toBeVisible(); +// }); + +// test('renders in STACKED_LARGE_VAL mode', () => { +// const props = getDefaultProps(); +// props.displayMode = 'STACKED_LARGE_VAL'; +// props.value = '+1234567890'; +// const { getByText } = render(); +// expect(getByText('+1234567890')).toBeVisible(); +// }); + +// test('does not invoke onBlur handler for readOnly fields', () => { +// const props = getDefaultProps(); +// props.readOnly = true; +// const { getByTestId, getByPlaceholderText } = render(); +// fireEvent.change(getByPlaceholderText('phone number'), { target: { value: '+1234567890' } }); +// fireEvent.blur(getByPlaceholderText('phone number')); +// expect(handleEvent).not.toHaveBeenCalled(); +// }); + +// test('invokes handlers for blur and change events', () => { +// const props = getDefaultProps(); +// const { getByTestId, getByPlaceholderText } = render(); +// fireEvent.change(getByPlaceholderText('phone number'), { target: { value: '+1234567890' } }); +// fireEvent.blur(getByPlaceholderText('phone number')); +// expect(handleEvent).toHaveBeenCalled(); +// }); +// }); + +// # Unit test suite for the Phone component + +import React from 'react'; +import { render, screen, fireEvent, cleanup } from '@testing-library/react'; +import Phone from '../../../../../src/components/field/Phone'; + +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(name => { + if (name === 'FieldValueList') { + return require('../FieldValueList').default; + } + return null; + }) +})); + +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: '.phoneInput' + }), + getValidationApi: () => ({ + validate + }), + updateDirtyCheckChangeList, + clearErrorMessages, + ignoreSuggestion, + acceptSuggestion + }) as any + ), + label: 'Phone Number', + required: false, + disabled: false, + value: '', + validatemessage: '', + status: '', + readOnly: false, + testId: 'phoneInputTestId', + helperText: 'Enter phone number', + displayMode: '', + hideLabel: false, + placeholder: 'Enter phone', + onChange: jest.fn() +}; + +describe('Phone Component', () => { + afterEach(() => { + cleanup(); + jest.clearAllMocks(); + }); + + test('renders with label and placeholder', () => { + render(); + expect(screen.getByLabelText('Phone Number')).toBeInTheDocument(); + expect(screen.getByPlaceholderText('Enter phone')).toBeInTheDocument(); + }); + + test('renders with required attribute', () => { + const props = { ...defaultProps, required: true }; + const { rerender } = render(); + const input = screen.getByTestId('phoneInputTestId'); + 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('phoneInputTestId'); + expect(input).toBeDisabled(); + + props.disabled = false; + rerender(); + expect(input).not.toBeDisabled(); + }); + + test('renders with readOnly attribute', () => { + const props = { ...defaultProps, readOnly: true }; + render(); + const input = screen.getByTestId('phoneInputTestId'); + expect(input).toHaveAttribute('readonly'); + }); + + test('renders with helper text', () => { + render(); + expect(screen.getByText('Enter phone number')).toBeInTheDocument(); + }); + + test('renders with validation message overriding helper text', () => { + const props = { ...defaultProps, validatemessage: 'Invalid phone' }; + render(); + expect(screen.getByText('Invalid phone')).toBeInTheDocument(); + expect(screen.queryByText('Enter phone number')).not.toBeInTheDocument(); + }); + + test('updates value on change', () => { + render(); + const input = screen.getByTestId('phoneInputTestId'); + fireEvent.change(input, { target: { value: '+1 555-123-4567' } }); + expect(input).toHaveValue('+1 555-123-4567'); + }); + + test('calls handleEvent on blur when not readOnly', () => { + render(); + const input = screen.getByTestId('phoneInputTestId'); + fireEvent.change(input, { target: { value: '+1 555-123-4567' } }); + fireEvent.blur(input); + expect(mockHandleEvent).toHaveBeenCalledWith(expect.any(Object), 'changeNblur', '.phoneInput', expect.stringContaining('+15551234567')); + }); + + test('does not call handleEvent on blur when readOnly', () => { + const props = { ...defaultProps, readOnly: true }; + render(); + const input = screen.getByTestId('phoneInputTestId'); + fireEvent.blur(input); + expect(mockHandleEvent).not.toHaveBeenCalled(); + }); + + test('renders in DISPLAY_ONLY mode', () => { + const props = { ...defaultProps, displayMode: 'DISPLAY_ONLY', value: '+1 555-123-4567' }; + render(); + expect(screen.getByText('+1 555-123-4567')).toBeInTheDocument(); + }); + + test('renders in STACKED_LARGE_VAL mode', () => { + const props = { ...defaultProps, displayMode: 'STACKED_LARGE_VAL', value: '+1 555-123-4567' }; + render(); + expect(screen.getByText('+1 555-123-4567')).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..dfbe0818 --- /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(