mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-24 19:19:57 +08:00
refactor: add optionType for Radio internally (#34849)
* refactor: add optionType for Radio internally * fix: lint * fix: update snapshot * feat: remove useless condition for RadioGroupContext * test: add test case for Radio.Button
This commit is contained in:
parent
452c5835ec
commit
afa4442a10
@ -19168,18 +19168,18 @@ exports[`ConfigProvider components Radio prefixCls 1`] = `
|
||||
class="prefix-Radio-group prefix-Radio-group-outline"
|
||||
>
|
||||
<label
|
||||
class="prefix-Radio-wrapper prefix-Radio-wrapper-checked"
|
||||
class="prefix-Radio-button-wrapper prefix-Radio-button-wrapper-checked"
|
||||
>
|
||||
<span
|
||||
class="prefix-Radio prefix-Radio-checked"
|
||||
class="prefix-Radio-button prefix-Radio-button-checked"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
class="prefix-Radio-input"
|
||||
class="prefix-Radio-button-input"
|
||||
type="radio"
|
||||
/>
|
||||
<span
|
||||
class="prefix-Radio-inner"
|
||||
class="prefix-Radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
|
@ -0,0 +1,87 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Radio Button rtl render component should be rendered correctly in RTL direction 1`] = `
|
||||
<label
|
||||
class="ant-radio-button-wrapper ant-radio-button-wrapper-rtl"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button"
|
||||
>
|
||||
<input
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value=""
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
</label>
|
||||
`;
|
||||
|
||||
exports[`Radio Button should render correctly 1`] = `
|
||||
<label
|
||||
class="ant-radio-button-wrapper customized"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button"
|
||||
>
|
||||
<input
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
Test
|
||||
</span>
|
||||
</label>
|
||||
`;
|
||||
|
||||
exports[`Radio Group passes prefixCls down to radio 1`] = `
|
||||
<div
|
||||
class="my-radio-group my-radio-group-outline"
|
||||
>
|
||||
<label
|
||||
class="my-radio-wrapper"
|
||||
>
|
||||
<span
|
||||
class="my-radio"
|
||||
>
|
||||
<input
|
||||
class="my-radio-input"
|
||||
type="radio"
|
||||
value="Apple"
|
||||
/>
|
||||
<span
|
||||
class="my-radio-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
Apple
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="my-radio-wrapper"
|
||||
style="font-size:12px"
|
||||
>
|
||||
<span
|
||||
class="my-radio"
|
||||
>
|
||||
<input
|
||||
class="my-radio-input"
|
||||
type="radio"
|
||||
value="Orange"
|
||||
/>
|
||||
<span
|
||||
class="my-radio-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
Orange
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
`;
|
250
components/radio/__tests__/radio-button.test.js
Normal file
250
components/radio/__tests__/radio-button.test.js
Normal file
@ -0,0 +1,250 @@
|
||||
import React from 'react';
|
||||
import { mount, render } from 'enzyme';
|
||||
import Radio, { Button } from '..';
|
||||
import focusTest from '../../../tests/shared/focusTest';
|
||||
import mountTest from '../../../tests/shared/mountTest';
|
||||
import rtlTest from '../../../tests/shared/rtlTest';
|
||||
|
||||
describe('Radio Button', () => {
|
||||
focusTest(Button, { refFocus: true });
|
||||
mountTest(Button);
|
||||
|
||||
rtlTest(Button);
|
||||
|
||||
it('should render correctly', () => {
|
||||
const wrapper = render(<Button className="customized">Test</Button>);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('responses hover events', () => {
|
||||
const onMouseEnter = jest.fn();
|
||||
const onMouseLeave = jest.fn();
|
||||
|
||||
const wrapper = mount(<Button onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} />);
|
||||
|
||||
wrapper.find('label').simulate('mouseenter');
|
||||
expect(onMouseEnter).toHaveBeenCalled();
|
||||
|
||||
wrapper.find('label').simulate('mouseleave');
|
||||
expect(onMouseLeave).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Radio Group', () => {
|
||||
function createRadioGroup(props) {
|
||||
return (
|
||||
<Radio.Group {...props}>
|
||||
<Button value="A">A</Button>
|
||||
<Button value="B">B</Button>
|
||||
<Button value="C">C</Button>
|
||||
</Radio.Group>
|
||||
);
|
||||
}
|
||||
|
||||
it('responses hover events', () => {
|
||||
const onMouseEnter = jest.fn();
|
||||
const onMouseLeave = jest.fn();
|
||||
|
||||
const wrapper = mount(
|
||||
<Radio.Group onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
|
||||
<Radio />
|
||||
</Radio.Group>,
|
||||
);
|
||||
|
||||
wrapper.find('div').at(0).simulate('mouseenter');
|
||||
expect(onMouseEnter).toHaveBeenCalled();
|
||||
|
||||
wrapper.find('div').at(0).simulate('mouseleave');
|
||||
expect(onMouseLeave).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('fire change events when value changes', () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const wrapper = mount(
|
||||
createRadioGroup({
|
||||
onChange,
|
||||
}),
|
||||
);
|
||||
const radios = wrapper.find('input');
|
||||
|
||||
// controlled component
|
||||
wrapper.setProps({ value: 'A' });
|
||||
radios.at(1).simulate('change');
|
||||
expect(onChange.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it('both of radio and radioGroup will trigger onchange event when they exists', () => {
|
||||
const onChange = jest.fn();
|
||||
const onChangeRadioGroup = jest.fn();
|
||||
|
||||
const wrapper = mount(
|
||||
<Radio.Group onChange={onChangeRadioGroup}>
|
||||
<Radio value="A" onChange={onChange}>
|
||||
A
|
||||
</Radio>
|
||||
<Radio value="B" onChange={onChange}>
|
||||
B
|
||||
</Radio>
|
||||
<Radio value="C" onChange={onChange}>
|
||||
C
|
||||
</Radio>
|
||||
</Radio.Group>,
|
||||
);
|
||||
const radios = wrapper.find('input');
|
||||
|
||||
// controlled component
|
||||
wrapper.setProps({ value: 'A' });
|
||||
radios.at(1).simulate('change');
|
||||
expect(onChange.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it('Trigger onChange when both of Button and radioGroup exists', () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const wrapper = mount(
|
||||
<Radio.Group onChange={onChange}>
|
||||
<Button value="A">A</Button>
|
||||
<Button value="B">B</Button>
|
||||
<Button value="C">C</Button>
|
||||
</Radio.Group>,
|
||||
);
|
||||
const radios = wrapper.find('input');
|
||||
|
||||
// controlled component
|
||||
wrapper.setProps({ value: 'A' });
|
||||
radios.at(1).simulate('change');
|
||||
expect(onChange.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should only trigger once when in group with options', () => {
|
||||
const onChange = jest.fn();
|
||||
const options = [{ label: 'Bamboo', value: 'Bamboo' }];
|
||||
const wrapper = mount(<Radio.Group options={options} onChange={onChange} />);
|
||||
|
||||
wrapper.find('input').simulate('change');
|
||||
expect(onChange).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("won't fire change events when value not changes", () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const wrapper = mount(
|
||||
createRadioGroup({
|
||||
onChange,
|
||||
}),
|
||||
);
|
||||
const radios = wrapper.find('input');
|
||||
|
||||
// controlled component
|
||||
wrapper.setProps({ value: 'A' });
|
||||
radios.at(0).simulate('change');
|
||||
expect(onChange.mock.calls.length).toBe(0);
|
||||
});
|
||||
|
||||
it('all children should have a name property', () => {
|
||||
const GROUP_NAME = 'radiogroup';
|
||||
const wrapper = mount(createRadioGroup({ name: GROUP_NAME }));
|
||||
|
||||
wrapper.find('input[type="radio"]').forEach(el => {
|
||||
expect(el.props().name).toEqual(GROUP_NAME);
|
||||
});
|
||||
});
|
||||
|
||||
it('passes prefixCls down to radio', () => {
|
||||
const options = [
|
||||
{ label: 'Apple', value: 'Apple' },
|
||||
{ label: 'Orange', value: 'Orange', style: { fontSize: 12 } },
|
||||
];
|
||||
const wrapper = render(<Radio.Group prefixCls="my-radio" options={options} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should forward ref', () => {
|
||||
let radioGroupRef;
|
||||
const wrapper = mount(
|
||||
createRadioGroup({
|
||||
ref: ref => {
|
||||
radioGroupRef = ref;
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(radioGroupRef).toBe(wrapper.children().getDOMNode());
|
||||
});
|
||||
|
||||
it('should support data-* or aria-* props', () => {
|
||||
const wrapper = mount(
|
||||
createRadioGroup({
|
||||
'data-radio-group-id': 'radio-group-id',
|
||||
'aria-label': 'radio-group',
|
||||
}),
|
||||
);
|
||||
expect(wrapper.getDOMNode().getAttribute('data-radio-group-id')).toBe('radio-group-id');
|
||||
expect(wrapper.getDOMNode().getAttribute('aria-label')).toBe('radio-group');
|
||||
});
|
||||
|
||||
it('Radio type should not be override', () => {
|
||||
const onChange = jest.fn();
|
||||
const wrapper = mount(
|
||||
<Radio.Group onChange={onChange}>
|
||||
<Radio value={1} type="1">
|
||||
A
|
||||
</Radio>
|
||||
<Radio value={2} type="2">
|
||||
B
|
||||
</Radio>
|
||||
<Radio value={3} type="3">
|
||||
C
|
||||
</Radio>
|
||||
<Radio value={4} type="4">
|
||||
D
|
||||
</Radio>
|
||||
</Radio.Group>,
|
||||
);
|
||||
const radios = wrapper.find('input');
|
||||
radios.at(1).simulate('change');
|
||||
expect(onChange).toHaveBeenCalled();
|
||||
expect(radios.at(1).getDOMNode().type).toBe('radio');
|
||||
});
|
||||
|
||||
describe('value is null or undefined', () => {
|
||||
it('use `defaultValue` when `value` is undefined', () => {
|
||||
const wrapper = mount(
|
||||
<Radio.Group defaultValue="bamboo" value={undefined}>
|
||||
<Button value="bamboo">Bamboo</Button>
|
||||
</Radio.Group>,
|
||||
);
|
||||
expect(
|
||||
wrapper
|
||||
.find('.ant-radio-button-wrapper')
|
||||
.at(0)
|
||||
.hasClass('ant-radio-button-wrapper-checked'),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
[undefined, null].forEach(newValue => {
|
||||
it(`should set value back when value change back to ${newValue}`, () => {
|
||||
const wrapper = mount(
|
||||
<Radio.Group value="bamboo">
|
||||
<Button value="bamboo">Bamboo</Button>
|
||||
</Radio.Group>,
|
||||
);
|
||||
expect(
|
||||
wrapper
|
||||
.find('.ant-radio-button-wrapper')
|
||||
.at(0)
|
||||
.hasClass('ant-radio-button-wrapper-checked'),
|
||||
).toBe(true);
|
||||
wrapper.setProps({ value: newValue });
|
||||
wrapper.update();
|
||||
expect(
|
||||
wrapper
|
||||
.find('.ant-radio-button-wrapper')
|
||||
.at(0)
|
||||
.hasClass('ant-radio-button-wrapper-checked'),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,8 +1,11 @@
|
||||
import * as React from 'react';
|
||||
import { RadioGroupContextProps } from './interface';
|
||||
import { RadioGroupContextProps, RadioOptionTypeContextProps } from './interface';
|
||||
|
||||
const RadioGroupContext = React.createContext<RadioGroupContextProps | null>(null);
|
||||
|
||||
export const RadioGroupContextProvider = RadioGroupContext.Provider;
|
||||
|
||||
export default RadioGroupContext;
|
||||
|
||||
export const RadioOptionTypeContext = React.createContext<RadioOptionTypeContextProps | null>(null);
|
||||
export const RadioOptionTypeContextProvider = RadioOptionTypeContext.Provider;
|
||||
|
@ -33,7 +33,6 @@ const RadioGroup = React.forwardRef<HTMLDivElement, RadioGroupProps>((props, ref
|
||||
prefixCls: customizePrefixCls,
|
||||
className = '',
|
||||
options,
|
||||
optionType,
|
||||
buttonStyle = 'outline' as RadioGroupButtonStyle,
|
||||
disabled,
|
||||
children,
|
||||
@ -48,14 +47,13 @@ const RadioGroup = React.forwardRef<HTMLDivElement, RadioGroupProps>((props, ref
|
||||
let childrenToRender = children;
|
||||
// 如果存在 options, 优先使用
|
||||
if (options && options.length > 0) {
|
||||
const optionsPrefixCls = optionType === 'button' ? `${prefixCls}-button` : prefixCls;
|
||||
childrenToRender = options.map(option => {
|
||||
if (typeof option === 'string' || typeof option === 'number') {
|
||||
// 此处类型自动推导为 string
|
||||
return (
|
||||
<Radio
|
||||
key={option.toString()}
|
||||
prefixCls={optionsPrefixCls}
|
||||
prefixCls={prefixCls}
|
||||
disabled={disabled}
|
||||
value={option}
|
||||
checked={value === option}
|
||||
@ -68,7 +66,7 @@ const RadioGroup = React.forwardRef<HTMLDivElement, RadioGroupProps>((props, ref
|
||||
return (
|
||||
<Radio
|
||||
key={`radio-group-value-options-${option.value}`}
|
||||
prefixCls={optionsPrefixCls}
|
||||
prefixCls={prefixCls}
|
||||
disabled={option.disabled || disabled}
|
||||
value={option.value}
|
||||
checked={value === option.value}
|
||||
@ -112,6 +110,7 @@ const RadioGroup = React.forwardRef<HTMLDivElement, RadioGroupProps>((props, ref
|
||||
value,
|
||||
disabled: props.disabled,
|
||||
name: props.name,
|
||||
optionType: props.optionType,
|
||||
}}
|
||||
>
|
||||
{renderGroup()}
|
||||
|
@ -25,6 +25,13 @@ export interface RadioGroupContextProps {
|
||||
value: any;
|
||||
disabled?: boolean;
|
||||
name?: string;
|
||||
/**
|
||||
* Control the appearance for Radio to display as button or not
|
||||
*
|
||||
* @default 'default'
|
||||
* @internal
|
||||
*/
|
||||
optionType?: RadioGroupOptionType;
|
||||
}
|
||||
|
||||
export type RadioProps = AbstractCheckboxProps<RadioChangeEvent>;
|
||||
@ -39,3 +46,5 @@ export interface RadioChangeEvent {
|
||||
preventDefault: () => void;
|
||||
nativeEvent: MouseEvent;
|
||||
}
|
||||
|
||||
export type RadioOptionTypeContextProps = RadioGroupOptionType;
|
||||
|
@ -6,11 +6,13 @@ import { useContext } from 'react';
|
||||
import { FormItemInputContext } from '../form/context';
|
||||
import { RadioProps, RadioChangeEvent } from './interface';
|
||||
import { ConfigContext } from '../config-provider';
|
||||
import RadioGroupContext from './context';
|
||||
import RadioGroupContext, { RadioOptionTypeContext } from './context';
|
||||
import devWarning from '../_util/devWarning';
|
||||
|
||||
const InternalRadio: React.ForwardRefRenderFunction<HTMLElement, RadioProps> = (props, ref) => {
|
||||
const context = React.useContext(RadioGroupContext);
|
||||
const groupContext = React.useContext(RadioGroupContext);
|
||||
const radioOptionTypeContext = React.useContext(RadioOptionTypeContext);
|
||||
|
||||
const { getPrefixCls, direction } = React.useContext(ConfigContext);
|
||||
const innerRef = React.useRef<HTMLElement>();
|
||||
const mergedRef = composeRef(ref, innerRef);
|
||||
@ -22,17 +24,22 @@ const InternalRadio: React.ForwardRefRenderFunction<HTMLElement, RadioProps> = (
|
||||
|
||||
const onChange = (e: RadioChangeEvent) => {
|
||||
props.onChange?.(e);
|
||||
context?.onChange?.(e);
|
||||
groupContext?.onChange?.(e);
|
||||
};
|
||||
|
||||
const { prefixCls: customizePrefixCls, className, children, style, ...restProps } = props;
|
||||
const prefixCls = getPrefixCls('radio', customizePrefixCls);
|
||||
const radioPrefixCls = getPrefixCls('radio', customizePrefixCls);
|
||||
const prefixCls =
|
||||
(groupContext?.optionType || radioOptionTypeContext) === 'button'
|
||||
? `${radioPrefixCls}-button`
|
||||
: radioPrefixCls;
|
||||
|
||||
const radioProps: RadioProps = { ...restProps };
|
||||
if (context) {
|
||||
radioProps.name = context.name;
|
||||
if (groupContext) {
|
||||
radioProps.name = groupContext.name;
|
||||
radioProps.onChange = onChange;
|
||||
radioProps.checked = props.value === context.value;
|
||||
radioProps.disabled = props.disabled || context.disabled;
|
||||
radioProps.checked = props.value === groupContext.value;
|
||||
radioProps.disabled = props.disabled || groupContext.disabled;
|
||||
}
|
||||
const wrapperClassString = classNames(
|
||||
`${prefixCls}-wrapper`,
|
||||
|
@ -3,21 +3,21 @@ import Radio from './radio';
|
||||
import { RadioChangeEvent } from './interface';
|
||||
import { AbstractCheckboxProps } from '../checkbox/Checkbox';
|
||||
import { ConfigContext } from '../config-provider';
|
||||
import RadioGroupContext from './context';
|
||||
import { RadioOptionTypeContextProvider } from './context';
|
||||
|
||||
export type RadioButtonProps = AbstractCheckboxProps<RadioChangeEvent>;
|
||||
|
||||
const RadioButton = (props: RadioButtonProps, ref: React.Ref<any>) => {
|
||||
const radioGroupContext = React.useContext(RadioGroupContext);
|
||||
const { getPrefixCls } = React.useContext(ConfigContext);
|
||||
|
||||
const { prefixCls: customizePrefixCls, ...radioProps } = props;
|
||||
const prefixCls = getPrefixCls('radio-button', customizePrefixCls);
|
||||
if (radioGroupContext) {
|
||||
radioProps.checked = props.value === radioGroupContext.value;
|
||||
radioProps.disabled = props.disabled || radioGroupContext.disabled;
|
||||
}
|
||||
return <Radio prefixCls={prefixCls} {...radioProps} type="radio" ref={ref} />;
|
||||
const prefixCls = getPrefixCls('radio', customizePrefixCls);
|
||||
|
||||
return (
|
||||
<RadioOptionTypeContextProvider value="button">
|
||||
<Radio prefixCls={prefixCls} {...radioProps} type="radio" ref={ref} />
|
||||
</RadioOptionTypeContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.forwardRef(RadioButton);
|
||||
|
Loading…
Reference in New Issue
Block a user