401 lines
14 KiB
TypeScript
401 lines
14 KiB
TypeScript
// 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<typeof OptionItem> {
|
|
return {
|
|
label: 'Test Option',
|
|
type: 'default',
|
|
testID: 'option-item',
|
|
};
|
|
}
|
|
|
|
it('renders correctly with default props', () => {
|
|
const props = getBaseProps();
|
|
const {getByTestId, getByText, queryByTestId} = renderWithIntlAndTheme(<OptionItem {...props}/>);
|
|
|
|
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(<OptionItem {...props}/>);
|
|
|
|
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(<OptionItem {...props}/>);
|
|
|
|
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(<OptionItem {...props}/>);
|
|
|
|
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(<OptionItem {...props}/>);
|
|
|
|
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 {...props}/>);
|
|
|
|
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(<OptionItem {...props}/>);
|
|
|
|
let label = getByText('Test Option');
|
|
expect(label).toHaveStyle({color: Preferences.THEMES.denim.dndIndicator});
|
|
|
|
props.destructive = false;
|
|
rerender(<OptionItem {...props}/>);
|
|
|
|
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(<OptionItem {...props}/>);
|
|
|
|
// No inline style yet
|
|
expect(getByText('Test Option')).toHaveStyle({fontWeight: '400'});
|
|
|
|
props.description = 'Test description';
|
|
rerender(<OptionItem {...props}/>);
|
|
|
|
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(<OptionItem {...props}/>);
|
|
|
|
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(<OptionItem {...props}/>);
|
|
|
|
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(<OptionItem {...props}/>);
|
|
|
|
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(<OptionItem {...props}/>);
|
|
|
|
let info = getByText('Destructive info');
|
|
expect(info).toHaveStyle({color: Preferences.THEMES.denim.dndIndicator});
|
|
|
|
props.destructive = false;
|
|
rerender(<OptionItem {...props}/>);
|
|
|
|
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(<OptionItem {...props}/>);
|
|
|
|
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(<OptionItem {...props}/>);
|
|
|
|
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(<OptionItem {...props}/>);
|
|
|
|
let selectedIcon = getByTestId('option-item.selected');
|
|
expect(selectedIcon).toBeTruthy();
|
|
|
|
props.selected = false;
|
|
rerender(<OptionItem {...props}/>);
|
|
|
|
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(<OptionItem {...props}/>);
|
|
|
|
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(<OptionItem {...props}/>);
|
|
|
|
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(<OptionItem {...props}/>);
|
|
|
|
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(<OptionItem {...props}/>);
|
|
|
|
let toggle = getByTestId('option-item.toggled.true.button');
|
|
expect(toggle).toBeTruthy();
|
|
|
|
props.selected = false;
|
|
rerender(<OptionItem {...props}/>);
|
|
|
|
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(<OptionItem {...props}/>);
|
|
|
|
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(<OptionItem {...props}/>);
|
|
|
|
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(<OptionItem {...props}/>);
|
|
|
|
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(<OptionItem {...props}/>);
|
|
|
|
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(<OptionItem {...props}/>);
|
|
|
|
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(<OptionItem {...props}/>);
|
|
|
|
const info = getByText('Test info');
|
|
expect(info).toHaveStyle({color: Preferences.THEMES.denim.dndIndicator});
|
|
});
|
|
});
|