From 1c3a4ba70993591b6e10bc9bc14dfc55dc5f9946 Mon Sep 17 00:00:00 2001 From: Dee <86516056+dheeraj1429@users.noreply.github.com> Date: Sun, 21 Sep 2025 23:08:01 +0530 Subject: [PATCH 1/3] [unit-testing] Working on unit testing --- App.tsx | 14 ++++- jest.config.js | 4 +- .../__test__/DropDown.test.tsx | 2 + .../__test__/ModalContainer.test.tsx | 20 ++++++- .../__test__/Pagination.test.tsx | 12 ++++ .../__test__/ProgressBar.test.tsx | 13 +++++ .../__test__/Radio.test.tsx | 54 +++++++++++++++++- .../__test__/SegmentedControl.test.tsx | 55 +++++++++++++++++++ .../__test__/Switch.test.tsx | 8 +-- .../__snapshots__/ProgressBar.test.tsx.snap | 21 +++++++ .../SegmentedControl.test.tsx.snap | 1 + .../__test__/useThemedProps.test.tsx | 23 +++----- .../src/components/Radio/Radio.tsx | 2 +- .../SegmentedControl/SegmentedControl.tsx | 15 ++--- 14 files changed, 208 insertions(+), 36 deletions(-) create mode 100644 src/packages/react-native-material-elements/__test__/ProgressBar.test.tsx create mode 100644 src/packages/react-native-material-elements/__test__/__snapshots__/ProgressBar.test.tsx.snap diff --git a/App.tsx b/App.tsx index 486e2a3..c9f3536 100644 --- a/App.tsx +++ b/App.tsx @@ -1,13 +1,21 @@ -import React from 'react'; +import React, { useState } from 'react'; import { SafeAreaView, ScrollView } from 'react-native'; -import { Container, ThemeProvider } from './src/packages/react-native-material-elements'; +import { Container, SegmentedControl, ThemeProvider } from './src/packages/react-native-material-elements'; function App(): React.JSX.Element { + const [index, setIndex] = useState(0); + return ( - + + setIndex(_index)} + /> + diff --git a/jest.config.js b/jest.config.js index 2f2e0f9..d4669e5 100644 --- a/jest.config.js +++ b/jest.config.js @@ -22,6 +22,8 @@ module.exports = { 'babel.config.js', ], + testPathIgnorePatterns: ['/node_modules', '/e2e'], + coverageReporters: ['html', 'text', 'lcov', 'text-summary'], moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node', 'mjs', 'svg', 'png'], @@ -32,7 +34,7 @@ module.exports = { coverageThreshold: { global: { statements: 80, - branches: 75, + branches: 80, functions: 80, lines: 80, }, diff --git a/src/packages/react-native-material-elements/__test__/DropDown.test.tsx b/src/packages/react-native-material-elements/__test__/DropDown.test.tsx index db60a29..d5a9e6a 100644 --- a/src/packages/react-native-material-elements/__test__/DropDown.test.tsx +++ b/src/packages/react-native-material-elements/__test__/DropDown.test.tsx @@ -9,6 +9,7 @@ describe('DropDown Component', () => { beforeEach(() => { jest.clearAllMocks(); + jest.clearAllTimers(); }); it('should render correctly', async () => { @@ -244,6 +245,7 @@ describe('DropDownListContainer component', () => { }); it('should call the onClose function when press on item', () => { + jest.useFakeTimers(); const { getByText } = render( { + const mockOnCloseHandler = jest.fn(); + + const mockTestId = 'mock-test-id'; + it('should render correctly with default props', () => { const { toJSON } = render( @@ -21,4 +25,18 @@ describe('ModalContainer', () => { const element = getByText('Hello'); expect(element).toBeDefined(); }); + + it('should call the onClose function', () => { + const { getByTestId } = render( + + <> + , + ); + + const item = getByTestId(mockTestId); + + fireEvent(item, 'press', { nativeEvent: {} }); + expect(mockOnCloseHandler).toHaveBeenCalled(); + expect(mockOnCloseHandler).toHaveBeenCalledTimes(1); + }); }); diff --git a/src/packages/react-native-material-elements/__test__/Pagination.test.tsx b/src/packages/react-native-material-elements/__test__/Pagination.test.tsx index 619bd33..78cb9b5 100644 --- a/src/packages/react-native-material-elements/__test__/Pagination.test.tsx +++ b/src/packages/react-native-material-elements/__test__/Pagination.test.tsx @@ -157,4 +157,16 @@ describe('Pagination Component', () => { expect(flattenedStyle.backgroundColor).toEqual('red'); expect(flattenedStyle.borderWidth).toEqual(2); }); + + it('should show all the pagination item if count is less than or equal to the max visible items, show all pages', () => { + const { getByText } = render(); + const firstItem = getByText('1'); + expect(firstItem).toBeDefined(); + + const secondItem = getByText('2'); + expect(secondItem).toBeDefined(); + + const thirdItem = getByText('3'); + expect(thirdItem).toBeDefined(); + }); }); diff --git a/src/packages/react-native-material-elements/__test__/ProgressBar.test.tsx b/src/packages/react-native-material-elements/__test__/ProgressBar.test.tsx new file mode 100644 index 0000000..1374dbd --- /dev/null +++ b/src/packages/react-native-material-elements/__test__/ProgressBar.test.tsx @@ -0,0 +1,13 @@ +import { ProgressBar } from '../src'; +import { render } from './test-utils'; + +describe('ProgressBar component', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should render correctly with default props', () => { + const { toJSON } = render(); + expect(toJSON()).toMatchSnapshot(); + }); +}); diff --git a/src/packages/react-native-material-elements/__test__/Radio.test.tsx b/src/packages/react-native-material-elements/__test__/Radio.test.tsx index fb2694e..891c41d 100644 --- a/src/packages/react-native-material-elements/__test__/Radio.test.tsx +++ b/src/packages/react-native-material-elements/__test__/Radio.test.tsx @@ -1,7 +1,8 @@ import React from 'react'; import { fireEvent, render, waitFor } from './test-utils'; -import { Radio, Text } from '../src'; +import { Radio, RadioCircle, Text } from '../src'; import { View } from 'react-native'; +import { RADIO_LARGE, RADIO_MEDIUM, RADIO_SMALL } from '../src/components/Radio/constants'; describe('Radio Component', () => { const mockRadioBaseButtonTestId = 'radio-base-button-test-id'; @@ -92,4 +93,55 @@ describe('Radio Component', () => { expect(queryByText('mock-label')).toBeNull(); expect(queryByText('mock-description')).toBeNull(); }); + + it('should render the divider component', () => { + const { getByTestId } = render(); + + const divider = getByTestId('divider'); + expect(divider).toBeDefined(); + }); +}); + +describe('Radio Circle', () => { + const testID = 'mock-test-id'; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should render small divider component', () => { + const { getByTestId } = render(); + + const radio = getByTestId(testID); + + expect(radio).toBeDefined(); + expect(radio.props.style.width).toEqual(RADIO_SMALL); + }); + + it('should render medium radioCircle component', () => { + const { getByTestId } = render(); + + const radio = getByTestId(testID); + + expect(radio).toBeDefined(); + expect(radio.props.style.width).toEqual(RADIO_MEDIUM); + }); + + it('should render large radioCircle component', () => { + const { getByTestId } = render(); + + const radio = getByTestId(testID); + + expect(radio).toBeDefined(); + expect(radio.props.style.width).toEqual(RADIO_LARGE); + }); + + it('should render small radioCircle component when invalid size passed', () => { + const { getByTestId } = render(); + + const radio = getByTestId(testID); + + expect(radio).toBeDefined(); + expect(radio.props.style.width).toEqual(RADIO_SMALL); + }); }); diff --git a/src/packages/react-native-material-elements/__test__/SegmentedControl.test.tsx b/src/packages/react-native-material-elements/__test__/SegmentedControl.test.tsx index b49d539..7093ebb 100644 --- a/src/packages/react-native-material-elements/__test__/SegmentedControl.test.tsx +++ b/src/packages/react-native-material-elements/__test__/SegmentedControl.test.tsx @@ -4,6 +4,10 @@ import { SegmentedControlItem } from '../src/components/SegmentedControl/Segment import { fireEvent, render } from './test-utils'; describe('SegmentedControl component', () => { + const mockSegmentedControllerTestId = 'mock-segmented-item-test-id'; + + const mockOnPress = jest.fn(); + beforeEach(() => { jest.clearAllMocks(); }); @@ -12,6 +16,57 @@ describe('SegmentedControl component', () => { const { toJSON } = render(); expect(toJSON()).toMatchSnapshot(); }); + + it('should call the onChange function', () => { + const { getByTestId } = render( + , + ); + + const firstItem = getByTestId(`${mockSegmentedControllerTestId}-0`); + + fireEvent(firstItem, 'press', { nativeEvent: {} }); + + expect(firstItem).toBeDefined(); + expect(mockOnPress).toHaveBeenCalled(); + expect(mockOnPress).toHaveBeenCalledTimes(1); + }); + + it('should apply the correct segmentTextStyle', () => { + const { getByText } = render( + , + ); + + const firstItem = getByText('First'); + expect(firstItem).toBeDefined(); + + expect(firstItem.props.style.color).toEqual('red'); + + const secondItem = getByText('Second'); + expect(secondItem).toBeDefined(); + + expect(secondItem.props.style.color).toEqual('red'); + }); + + it('should apply the correct segmentTextStyle to specific item', () => { + const { getByText } = render( + , + ); + + const firstItem = getByText('First'); + expect(firstItem).toBeDefined(); + + expect(firstItem.props.style.color).toEqual('red'); + }); }); describe('SegmentedControlContainer component', () => { diff --git a/src/packages/react-native-material-elements/__test__/Switch.test.tsx b/src/packages/react-native-material-elements/__test__/Switch.test.tsx index 71add79..39f9b98 100644 --- a/src/packages/react-native-material-elements/__test__/Switch.test.tsx +++ b/src/packages/react-native-material-elements/__test__/Switch.test.tsx @@ -22,7 +22,7 @@ import { SWITCH_THUMB_WIDTH_MEDIUM, SWITCH_THUMB_WIDTH_SMALL, } from '../src'; -import { fireEvent, render, waitFor } from './test-utils'; +import { fireEvent, render } from './test-utils'; describe('Switch Component', () => { const switchMockTestId = 'switch-test-id'; @@ -33,11 +33,9 @@ describe('Switch Component', () => { jest.clearAllMocks(); }); - it('should match the snapshot with default props', async () => { + it('should match the snapshot with default props', () => { const { toJSON } = render(); - await waitFor(() => { - expect(toJSON()).toMatchSnapshot(); - }); + expect(toJSON()).toMatchSnapshot(); }); it('should forward ref correctly', () => { diff --git a/src/packages/react-native-material-elements/__test__/__snapshots__/ProgressBar.test.tsx.snap b/src/packages/react-native-material-elements/__test__/__snapshots__/ProgressBar.test.tsx.snap new file mode 100644 index 0000000..35921c3 --- /dev/null +++ b/src/packages/react-native-material-elements/__test__/__snapshots__/ProgressBar.test.tsx.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ProgressBar component should render correctly with default props 1`] = ` + + + +`; diff --git a/src/packages/react-native-material-elements/__test__/__snapshots__/SegmentedControl.test.tsx.snap b/src/packages/react-native-material-elements/__test__/__snapshots__/SegmentedControl.test.tsx.snap index 4b6247c..5d71c3d 100644 --- a/src/packages/react-native-material-elements/__test__/__snapshots__/SegmentedControl.test.tsx.snap +++ b/src/packages/react-native-material-elements/__test__/__snapshots__/SegmentedControl.test.tsx.snap @@ -104,6 +104,7 @@ exports[`SegmentedControl component should render correctly with default props 1 "zIndex": 10, } } + testID="undefined-0" > { + beforeAll(() => { + jest.spyOn(console, 'warn').mockImplementation(() => {}); + }); + it('should match icon component snapshot correctly', () => { const props = { icon: () => , }; const { result } = renderHook(() => useThemedProps(props), { wrapper: ThemeWrapper }); - const { toJSON } = render(result.current.icon); + const { toJSON } = render(result.current.icon as any); expect(toJSON()).toMatchSnapshot(); }); @@ -41,7 +45,7 @@ describe('useThemedProps', () => { const props = {}; const { result } = renderHook(() => useThemedProps(props), { wrapper: ThemeWrapper }); - expect(result.current.icon).toBeUndefined(); + expect(result.current.icon as any).toBeUndefined(); }); it('should support label as a ReactNode', () => { @@ -82,7 +86,7 @@ describe('useThemedProps', () => { const { result } = renderHook(() => useThemedProps(props), { wrapper: ThemeWrapper }); - const iconComponent = result.current.icon; + const iconComponent = result.current.icon as any; expect(iconComponent).toBeDefined(); expect(iconComponent.props.style.backgroundColor).toEqual(green[500]); @@ -96,7 +100,7 @@ describe('useThemedProps', () => { const iconComponent = result.current.icon; expect(iconComponent).toBeDefined(); - const { toJSON } = render(iconComponent); + const { toJSON } = render(iconComponent as any); expect(toJSON()).toMatchSnapshot(); }); @@ -188,15 +192,9 @@ describe('useThemedProps', () => { it('should warn if icon is a primitive value', () => { const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); - - const props = { - icon: 'not-a-component' as any, - }; - + const props = { icon: 'not-a-component' as any }; renderHook(() => useThemedProps(props), { wrapper: ThemeWrapper }); - expect(warnSpy).toHaveBeenCalledWith('icon prop must be either or () => . Other values are not valid.'); - warnSpy.mockRestore(); }); @@ -206,11 +204,8 @@ describe('useThemedProps', () => { const props = { icon: { something: true } as any, }; - renderHook(() => useThemedProps(props), { wrapper: ThemeWrapper }); - expect(warnSpy).toHaveBeenCalledWith('icon prop must be either or () => . Other values are not valid.'); - warnSpy.mockRestore(); }); diff --git a/src/packages/react-native-material-elements/src/components/Radio/Radio.tsx b/src/packages/react-native-material-elements/src/components/Radio/Radio.tsx index 78266c8..63fe739 100644 --- a/src/packages/react-native-material-elements/src/components/Radio/Radio.tsx +++ b/src/packages/react-native-material-elements/src/components/Radio/Radio.tsx @@ -311,7 +311,7 @@ const RadioOutline: React.FC = ({ style, isActive, children, ); }; -const RadioCircle: React.FC = ({ +export const RadioCircle: React.FC = ({ style, variant, isActive, diff --git a/src/packages/react-native-material-elements/src/components/SegmentedControl/SegmentedControl.tsx b/src/packages/react-native-material-elements/src/components/SegmentedControl/SegmentedControl.tsx index 846b0b4..9d70d8a 100644 --- a/src/packages/react-native-material-elements/src/components/SegmentedControl/SegmentedControl.tsx +++ b/src/packages/react-native-material-elements/src/components/SegmentedControl/SegmentedControl.tsx @@ -48,6 +48,7 @@ export interface SegmentedControlProps applySegmentItemTextStyleIndex?: number; /** View styles for animated segment */ animatedSegmentStyle?: ViewStyle; + segmentedControlItemTestId?: string; } export const SegmentedControl = ({ @@ -62,6 +63,7 @@ export const SegmentedControl = ({ animatedSegmentStyle, segmentItemContainerStyles, selectedIndex = 0, + segmentedControlItemTestId, ...props }: SegmentedControlProps) => { const animatedSegmentWidth = useRef(new Animated.Value(0)); @@ -70,15 +72,11 @@ export const SegmentedControl = ({ const colorScheme = useColorScheme(); - const [selectedSegment, setSelectedSegment] = useState>(data[0]); const [segmentRect, setSegmentRect] = useState(null); const segmentedItemHandler = function (value: Partial, index: number) { - if (selectedSegment !== value) { - setSelectedSegment(value); - if (onChange) { - onChange(value, index); - } + if (onChange) { + onChange(value, index); } }; @@ -131,10 +129,6 @@ export const SegmentedControl = ({ toValue: width * selectedIndex, }).start(); } - - if (selectedIndex && selectedIndex < data.length) { - setSelectedSegment(data[selectedIndex]); - } }, [segmentRect, selectedIndex, data]); return ( @@ -151,6 +145,7 @@ export const SegmentedControl = ({ headingStyles={getSegmentItemHeadingStyle(index)} style={getSegmentItemStyle(index)} segmentItemContainerStyles={segmentItemContainerStyles} + testID={`${segmentedControlItemTestId}-${index}`} /> ))} From f5ee5af7610f63af75aa2db5735ccaa8e57e05f8 Mon Sep 17 00:00:00 2001 From: Dee <86516056+dheeraj1429@users.noreply.github.com> Date: Mon, 22 Sep 2025 23:24:44 +0530 Subject: [PATCH 2/3] [unit-testing] Fixed unit test cases. Fix sonar issues --- .gitignore | 3 + jest.config.js | 2 +- package.json | 3 +- .../__test__/Button.test.tsx | 33 ++++++++++- .../__test__/ProgressBar.test.tsx | 2 + .../__test__/Radio.test.tsx | 55 +++++++++++++++++++ .../__test__/SegmentedControl.test.tsx | 36 ++++++++++++ .../__test__/V2ThemeContext.test.tsx | 2 + .../__snapshots__/Radio.test.tsx.snap | 1 + .../SegmentedControl.test.tsx.snap | 2 +- .../__test__/utils.test.ts | 30 +--------- .../components/Accordion/AccordionSummary.tsx | 3 - .../src/components/Alert/Alert.tsx | 41 +------------- .../src/components/Alert/Alert.types.d.ts | 38 +++++++++++++ .../src/components/Alert/utils.ts | 6 +- .../src/components/Badge/Badge.tsx | 4 +- .../src/components/Button/Button.tsx | 2 + .../src/components/Button/Button.types.d.ts | 4 ++ .../src/components/Grid/FlatGird.tsx | 4 +- .../src/components/List/List.style.ts | 12 +++- .../src/components/Radio/Radio.tsx | 8 ++- .../src/components/Ripple/Ripple.tsx | 3 +- .../src/components/Ripple/Ripple.types.d.ts | 2 +- .../SegmentedControl/SegmentedControl.tsx | 2 +- .../src/components/Snackbar/Snackbar.tsx | 2 +- .../src/components/TextField/TextField.tsx | 2 +- .../TextField/TextFieldEndAdornment.tsx | 10 +--- .../src/hooks/useRestyle.ts | 12 ++-- .../src/libraries/themes/v1/colors/colors.ts | 2 +- .../src/utils/index.ts | 1 - .../src/utils/randomId.ts | 5 -- 31 files changed, 222 insertions(+), 110 deletions(-) create mode 100644 src/packages/react-native-material-elements/src/components/Alert/Alert.types.d.ts delete mode 100644 src/packages/react-native-material-elements/src/utils/randomId.ts diff --git a/.gitignore b/.gitignore index bbf7688..5a3e6af 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,9 @@ local.properties *.keystore !debug.keystore +# sonar scan +.scannerwork + # node.js # node_modules/ diff --git a/jest.config.js b/jest.config.js index d4669e5..d8dd005 100644 --- a/jest.config.js +++ b/jest.config.js @@ -34,7 +34,7 @@ module.exports = { coverageThreshold: { global: { statements: 80, - branches: 80, + branches: 79, functions: 80, lines: 80, }, diff --git a/package.json b/package.json index bf00d93..8c9c425 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "ios": "react-native run-ios", "lint": "eslint .", "start": "react-native start", - "test": "jest --coverage" + "test": "jest --coverage", + "sonar-scanner": "sonar-scanner" }, "dependencies": { "lodash": "^4.17.21", diff --git a/src/packages/react-native-material-elements/__test__/Button.test.tsx b/src/packages/react-native-material-elements/__test__/Button.test.tsx index cb2994e..dc34171 100644 --- a/src/packages/react-native-material-elements/__test__/Button.test.tsx +++ b/src/packages/react-native-material-elements/__test__/Button.test.tsx @@ -1,7 +1,7 @@ import { render as testRenderer, waitFor } from '@testing-library/react-native'; import React from 'react'; import { Text, View } from 'react-native'; -import { Button, green, lightBlue, primary, red, secondary, ThemeProvider, yellow } from '../src'; +import { Button, gray, green, lightBlue, primary, red, secondary, ThemeProvider, yellow } from '../src'; import { fireEvent, render } from './test-utils'; describe('Button', () => { @@ -427,4 +427,35 @@ describe('Button', () => { const labelText = getByText(mockLabel); expect(labelText.props.style).toEqual(expect.objectContaining({ color: 'red', fontWeight: 100 })); }); + + it('should show the dark gray color of the text label when button color is lightGray', () => { + const { getByText } = render(