// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import React, {type ComponentProps} from 'react'; import UserChip from '@components/chips/user_chip'; import Preferences from '@constants/preferences'; import {act, fireEvent, renderWithIntlAndTheme} from '@test/intl-test-helper'; import TestHelper from '@test/test_helper'; import OptionIcon from './option_icon'; import RadioItem from './radio_item'; import OptionItem from './index'; jest.mock('./option_icon'); jest.mocked(OptionIcon).mockImplementation((props) => React.createElement('OptionIcon', {...props, testID: 'option-item.icon.mock'})); jest.mock('@components/chips/user_chip'); jest.mocked(UserChip).mockImplementation((props) => React.createElement('UserChip', {...props, testID: 'option-item.user_chip.mock'})); jest.mock('./radio_item'); jest.mocked(RadioItem).mockImplementation((props) => React.createElement('RadioItem', props)); describe('OptionItem', () => { function getBaseProps(): ComponentProps { return { label: 'Test Option', type: 'default', testID: 'option-item', }; } it('renders correctly with default props', () => { const props = getBaseProps(); const {getByTestId, getByText, queryByTestId} = renderWithIntlAndTheme(); const optionItem = getByTestId('option-item'); expect(optionItem).toBeTruthy(); const label = getByText('Test Option'); expect(label).toBeTruthy(); expect(label).toHaveStyle({fontWeight: '400'}); expect(label.props.numberOfLines).toBe(2); // No description expect(queryByTestId('option-item.description')).toBeNull(); // No radio component expect(queryByTestId('option-item.selected')).toBeNull(); expect(queryByTestId('option-item.not_selected')).toBeNull(); // No icon expect(queryByTestId('option-item.icon.mock')).toBeNull(); // No user chip expect(queryByTestId('option-item.user_chip.mock')).toBeNull(); // No info expect(queryByTestId('option-item.info')).toBeNull(); // No action whatsoever expect(queryByTestId('option-item.selected')).toBeNull(); expect(queryByTestId('option-item.toggled.false.button')).toBeNull(); expect(queryByTestId('option-item.toggled.true.button')).toBeNull(); expect(queryByTestId('option-item.remove.button')).toBeNull(); expect(queryByTestId('option-item.arrow.icon')).toBeNull(); }); it('respects custom label number of lines', () => { const props = getBaseProps(); props.labelNumberOfLines = 1; const {getByText} = renderWithIntlAndTheme(); const label = getByText('Test Option'); expect(label.props.numberOfLines).toBe(1); }); it('renders with description when provided', () => { const props = getBaseProps(); props.description = 'Test description'; const {getByText, rerender} = renderWithIntlAndTheme(); let description = getByText('Test description'); expect(description).toBeTruthy(); expect(description.props.numberOfLines).toBeUndefined(); expect(description).not.toHaveStyle({color: Preferences.THEMES.denim.dndIndicator}); props.descriptionNumberOfLines = 2; props.destructive = true; rerender(); description = getByText('Test description'); expect(description).toBeTruthy(); expect(description.props.numberOfLines).toBe(2); expect(description).toHaveStyle({color: Preferences.THEMES.denim.dndIndicator}); }); it('renders with icon when provided', () => { const props = getBaseProps(); props.icon = 'test-icon'; props.iconColor = '#ff0000'; const {getByTestId, rerender} = renderWithIntlAndTheme(); let optionItem = getByTestId('option-item.icon.mock'); expect(optionItem.props.icon).toBe('test-icon'); expect(optionItem.props.iconColor).toBe('#ff0000'); expect(optionItem.props.destructive).toBeFalsy(); props.destructive = true; rerender(); optionItem = getByTestId('option-item.icon.mock'); expect(optionItem.props.destructive).toBeTruthy(); }); it('renders with destructive styling when destructive is true', () => { const props = getBaseProps(); props.destructive = true; const {getByText, rerender} = renderWithIntlAndTheme(); let label = getByText('Test Option'); expect(label).toHaveStyle({color: Preferences.THEMES.denim.dndIndicator}); props.destructive = false; rerender(); label = getByText('Test Option'); expect(label).not.toHaveStyle({color: Preferences.THEMES.denim.dndIndicator}); }); it('renders with inline layout when inline is true and description exists', () => { const props = getBaseProps(); props.inline = true; const {getByText, rerender} = renderWithIntlAndTheme(); // No inline style yet expect(getByText('Test Option')).toHaveStyle({fontWeight: '400'}); props.description = 'Test description'; rerender(); expect(getByText('Test Option')).toHaveStyle({fontWeight: '600'}); expect(getByText('Test description')).toHaveStyle({color: Preferences.THEMES.denim.centerChannelColor}); }); it('renders description as destructive only if nonDestructiveDescription is false', () => { const props = getBaseProps(); props.description = 'Test description'; props.destructive = true; props.nonDestructiveDescription = false; const {rerender, getByText} = renderWithIntlAndTheme(); let label = getByText('Test Option'); let description = getByText('Test description'); expect(label).toHaveStyle({color: Preferences.THEMES.denim.dndIndicator}); expect(description).toHaveStyle({color: Preferences.THEMES.denim.dndIndicator}); props.nonDestructiveDescription = true; rerender(); label = getByText('Test Option'); description = getByText('Test description'); expect(label).toHaveStyle({color: Preferences.THEMES.denim.dndIndicator}); expect(description).not.toHaveStyle({color: Preferences.THEMES.denim.dndIndicator}); }); it('renders with info text when provided', () => { const props = getBaseProps(); props.info = 'Additional info'; const {getByText} = renderWithIntlAndTheme(); const info = getByText('Additional info'); expect(info).toBeTruthy(); }); it('renders with destructive info styling when destructive is true', () => { const props = getBaseProps(); props.info = 'Destructive info'; props.destructive = true; const {getByText, rerender} = renderWithIntlAndTheme(); let info = getByText('Destructive info'); expect(info).toHaveStyle({color: Preferences.THEMES.denim.dndIndicator}); props.destructive = false; rerender(); info = getByText('Destructive info'); expect(info).not.toHaveStyle({color: Preferences.THEMES.denim.dndIndicator}); }); it('renders with user chip when info is UserChipData', () => { const props = getBaseProps(); props.info = { user: TestHelper.fakeUserModel(), onPress: jest.fn(), teammateNameDisplay: 'nickname_full_name', }; const {getByTestId} = renderWithIntlAndTheme(); const userChip = getByTestId('option-item.user_chip.mock'); expect(userChip).toBeTruthy(); expect(userChip.props.user).toBe(props.info.user); expect(userChip.props.onPress).toBe(props.info.onPress); expect(userChip.props.teammateNameDisplay).toBe(props.info.teammateNameDisplay); }); it('shows link type correctly', () => { const props = getBaseProps(); props.type = 'link'; props.label = 'Test Link'; const {getByText} = renderWithIntlAndTheme(); const link = getByText('Test Link'); expect(link).toBeTruthy(); expect(link).toHaveStyle({color: Preferences.THEMES.denim.linkColor}); }); it('shows select type correctly', () => { const props = getBaseProps(); props.type = 'select'; props.selected = true; const {getByTestId, rerender, queryByTestId} = renderWithIntlAndTheme(); let selectedIcon = getByTestId('option-item.selected'); expect(selectedIcon).toBeTruthy(); props.selected = false; rerender(); selectedIcon = queryByTestId('option-item.selected'); expect(selectedIcon).toBeNull(); }); it('shows radio type correctly', () => { const props = getBaseProps(); props.type = 'radio'; props.selected = true; props.isRadioCheckmark = true; const {getByTestId, rerender, queryByTestId} = renderWithIntlAndTheme(); let radioItem = getByTestId('option-item.selected'); expect(radioItem).toBeTruthy(); expect(radioItem.props.checkedBody).toBe(true); expect(radioItem.props.selected).toBe(true); props.selected = false; rerender(); radioItem = queryByTestId('option-item.not_selected'); expect(radioItem).toBeTruthy(); expect(radioItem.props.checkedBody).toBe(true); expect(radioItem.props.selected).toBe(false); props.isRadioCheckmark = false; rerender(); radioItem = queryByTestId('option-item.not_selected'); expect(radioItem).toBeTruthy(); expect(radioItem.props.checkedBody).toBe(false); expect(radioItem.props.selected).toBe(false); }); it('shows toggle type correctly', () => { const props = getBaseProps(); props.type = 'toggle'; props.selected = true; props.action = jest.fn(); const {getByTestId, rerender} = renderWithIntlAndTheme(); let toggle = getByTestId('option-item.toggled.true.button'); expect(toggle).toBeTruthy(); props.selected = false; rerender(); toggle = getByTestId('option-item.toggled.false.button'); expect(toggle).toBeTruthy(); }); it('calls action for toggles', () => { const props = getBaseProps(); props.type = 'toggle'; props.selected = true; props.action = jest.fn(); const {getByTestId} = renderWithIntlAndTheme(); const toggle = getByTestId('option-item.toggled.true.button'); act(() => { fireEvent(toggle, 'valueChange', false); }); expect(props.action).toHaveBeenCalledWith(false); act(() => { fireEvent(toggle, 'valueChange', true); }); expect(props.action).toHaveBeenCalledWith(true); }); it('doesnt call action for toggles when text is pressed', () => { const props = getBaseProps(); props.type = 'toggle'; props.selected = true; props.action = jest.fn(); const {getByTestId} = renderWithIntlAndTheme(); const optionItem = getByTestId('option-item'); expect(optionItem).toBeTruthy(); act(() => { fireEvent.press(optionItem); }); expect(props.action).not.toHaveBeenCalled(); const toggle = getByTestId('option-item.toggled.true.button'); act(() => { fireEvent(toggle, 'valueChange', false); }); expect(props.action).toHaveBeenCalled(); }); it('shows arrow type correctly', () => { const props = getBaseProps(); props.type = 'arrow'; const {getByTestId} = renderWithIntlAndTheme(); const arrowIcon = getByTestId('option-item.arrow.icon'); expect(arrowIcon).toBeTruthy(); }); it('shows remove type correctly', () => { const props = getBaseProps(); props.type = 'remove'; props.action = jest.fn(); props.onRemove = jest.fn(); const {getByTestId} = renderWithIntlAndTheme(); const removeButton = getByTestId('option-item.remove.button'); expect(removeButton).toBeTruthy(); act(() => { fireEvent.press(removeButton); }); expect(props.onRemove).toHaveBeenCalled(); expect(props.action).not.toHaveBeenCalled(); jest.mocked(props.onRemove).mockClear(); jest.mocked(props.action).mockClear(); act(() => { fireEvent.press(getByTestId('option-item')); }); expect(props.action).toHaveBeenCalled(); expect(props.onRemove).not.toHaveBeenCalled(); }); it('doesnt call action for none type', () => { const props = getBaseProps(); props.type = 'none'; props.action = jest.fn(); const {getByTestId} = renderWithIntlAndTheme(); const optionItem = getByTestId('option-item'); expect(optionItem).toBeTruthy(); act(() => { fireEvent.press(optionItem); }); expect(props.action).not.toHaveBeenCalled(); }); it('should show destructive info styling when isInfoDestructive is true', () => { const props = getBaseProps(); props.info = 'Test info'; props.isInfoDestructive = true; props.destructive = false; const {getByText} = renderWithIntlAndTheme(); const info = getByText('Test info'); expect(info).toHaveStyle({color: Preferences.THEMES.denim.dndIndicator}); }); });