mirror of
https://github.com/zoriya/react-native-web.git
synced 2026-05-25 15:42:24 +00:00
[change] modernize Switch
Rewrite Switch to use function components and hooks. Rewrite the tests to replace enzyme with testing-library.
This commit is contained in:
@@ -1,54 +1,58 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { render } from '@testing-library/react';
|
||||
import Switch from '..';
|
||||
|
||||
const checkboxSelector = 'input[type="checkbox"]';
|
||||
function findCheckbox(container) {
|
||||
return container.firstChild.querySelector('input');
|
||||
}
|
||||
|
||||
describe('components/Switch', () => {
|
||||
test('accessibilityLabel is applied to native checkbox', () => {
|
||||
const component = shallow(<Switch accessibilityLabel="switch" />);
|
||||
expect(component.find(checkboxSelector).prop('aria-label')).toBe('switch');
|
||||
const { container } = render(<Switch accessibilityLabel="switch" />);
|
||||
expect(findCheckbox(container).getAttribute('aria-label')).toBe('switch');
|
||||
});
|
||||
|
||||
describe('disabled', () => {
|
||||
test('when "false" a default checkbox is rendered', () => {
|
||||
const component = shallow(<Switch />);
|
||||
expect(component.find(checkboxSelector).prop('disabled')).toBe(undefined);
|
||||
const { container } = render(<Switch />);
|
||||
expect(findCheckbox(container).disabled).toBe(false);
|
||||
});
|
||||
|
||||
test('when "true" a disabled checkbox is rendered', () => {
|
||||
const component = shallow(<Switch disabled />);
|
||||
expect(component.find(checkboxSelector).prop('disabled')).toBe(true);
|
||||
const { container } = render(<Switch disabled />);
|
||||
expect(findCheckbox(container).disabled).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onValueChange', () => {
|
||||
test('when value is "false" it receives "true"', () => {
|
||||
const onValueChange = jest.fn();
|
||||
const component = shallow(<Switch onValueChange={onValueChange} value={false} />);
|
||||
component.find('input').simulate('change', { nativeEvent: { target: { checked: true } } });
|
||||
const { container } = render(<Switch onValueChange={onValueChange} value={false} />);
|
||||
const checkbox = findCheckbox(container);
|
||||
checkbox.click(); // Needed to get ReactDOM to trigger 'change' event
|
||||
expect(onValueChange).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
test('when value is "true" it receives "false"', () => {
|
||||
const onValueChange = jest.fn();
|
||||
const component = shallow(<Switch onValueChange={onValueChange} value />);
|
||||
component.find('input').simulate('change', { nativeEvent: { target: { checked: false } } });
|
||||
const { container } = render(<Switch onValueChange={onValueChange} value />);
|
||||
const checkbox = findCheckbox(container);
|
||||
checkbox.click(); // Needed to get ReactDOM to trigger 'change' event
|
||||
expect(onValueChange).toHaveBeenCalledWith(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('value', () => {
|
||||
test('when "false" an unchecked checkbox is rendered', () => {
|
||||
const component = shallow(<Switch value={false} />);
|
||||
expect(component.find(checkboxSelector).prop('checked')).toBe(false);
|
||||
const { container } = render(<Switch value={false} />);
|
||||
expect(findCheckbox(container).checked).toBe(false);
|
||||
});
|
||||
|
||||
test('when "true" a checked checkbox is rendered', () => {
|
||||
const component = shallow(<Switch value />);
|
||||
expect(component.find(checkboxSelector).prop('checked')).toBe(true);
|
||||
const { container } = render(<Switch value />);
|
||||
expect(findCheckbox(container).checked).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+106
-98
@@ -10,13 +10,12 @@
|
||||
import type { ColorValue } from '../../types';
|
||||
import type { ViewProps } from '../View';
|
||||
|
||||
import applyNativeMethods from '../../modules/applyNativeMethods';
|
||||
import createElement from '../createElement';
|
||||
import multiplyStyleLengthValue from '../../modules/multiplyStyleLengthValue';
|
||||
import StyleSheet from '../StyleSheet';
|
||||
import UIManager from '../UIManager';
|
||||
import View from '../View';
|
||||
import React from 'react';
|
||||
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
|
||||
|
||||
type SwitchProps = {
|
||||
...ViewProps,
|
||||
@@ -33,110 +32,119 @@ const emptyObject = {};
|
||||
const thumbDefaultBoxShadow = '0px 1px 3px rgba(0,0,0,0.5)';
|
||||
const thumbFocusedBoxShadow = `${thumbDefaultBoxShadow}, 0 0 0 10px rgba(0,0,0,0.1)`;
|
||||
|
||||
class Switch extends React.Component<SwitchProps> {
|
||||
_checkboxElement: HTMLInputElement;
|
||||
_thumbElement: View;
|
||||
const Switch = forwardRef<SwitchProps, *>((props, ref) => {
|
||||
const {
|
||||
accessibilityLabel,
|
||||
activeThumbColor = '#009688',
|
||||
activeTrackColor = '#A3D3CF',
|
||||
disabled = false,
|
||||
onValueChange,
|
||||
style = emptyObject,
|
||||
thumbColor = '#FAFAFA',
|
||||
trackColor = '#939393',
|
||||
value = false,
|
||||
...other
|
||||
} = props;
|
||||
|
||||
static displayName = 'Switch';
|
||||
const checkboxRef = useRef(null);
|
||||
const thumbRef = useRef(null);
|
||||
|
||||
blur() {
|
||||
UIManager.blur(this._checkboxElement);
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => {
|
||||
return {
|
||||
blur() {
|
||||
UIManager.blur(checkboxRef.current);
|
||||
},
|
||||
focus() {
|
||||
UIManager.focus(checkboxRef.current);
|
||||
}
|
||||
};
|
||||
},
|
||||
[checkboxRef]
|
||||
);
|
||||
|
||||
function handleChange(event: Object) {
|
||||
if (onValueChange != null) {
|
||||
onValueChange(event.nativeEvent.target.checked);
|
||||
}
|
||||
}
|
||||
|
||||
focus() {
|
||||
UIManager.focus(this._checkboxElement);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
accessibilityLabel,
|
||||
activeThumbColor = '#009688',
|
||||
activeTrackColor = '#A3D3CF',
|
||||
disabled = false,
|
||||
onValueChange, // eslint-disable-line
|
||||
style = emptyObject,
|
||||
thumbColor = '#FAFAFA',
|
||||
trackColor = '#939393',
|
||||
value = false,
|
||||
...other
|
||||
} = this.props;
|
||||
|
||||
const { height: styleHeight, width: styleWidth } = StyleSheet.flatten(style);
|
||||
const height = styleHeight || 20;
|
||||
const minWidth = multiplyStyleLengthValue(height, 2);
|
||||
const width = styleWidth > minWidth ? styleWidth : minWidth;
|
||||
const trackBorderRadius = multiplyStyleLengthValue(height, 0.5);
|
||||
const trackCurrentColor = value
|
||||
? (trackColor != null && typeof trackColor === 'object' && trackColor.true) ||
|
||||
activeTrackColor
|
||||
: (trackColor != null && typeof trackColor === 'object' && trackColor.false) || trackColor;
|
||||
const thumbCurrentColor = value ? activeThumbColor : thumbColor;
|
||||
const thumbHeight = height;
|
||||
const thumbWidth = thumbHeight;
|
||||
|
||||
const rootStyle = [styles.root, style, disabled && styles.cursorDefault, { height, width }];
|
||||
|
||||
const trackStyle = [
|
||||
styles.track,
|
||||
{
|
||||
backgroundColor: disabled ? '#D5D5D5' : trackCurrentColor,
|
||||
borderRadius: trackBorderRadius
|
||||
}
|
||||
];
|
||||
|
||||
const thumbStyle = [
|
||||
styles.thumb,
|
||||
value && styles.thumbActive,
|
||||
{
|
||||
backgroundColor: disabled ? '#BDBDBD' : thumbCurrentColor,
|
||||
height: thumbHeight,
|
||||
marginStart: value ? multiplyStyleLengthValue(thumbWidth, -1) : 0,
|
||||
width: thumbWidth
|
||||
}
|
||||
];
|
||||
|
||||
const nativeControl = createElement('input', {
|
||||
accessibilityLabel,
|
||||
checked: value,
|
||||
disabled: disabled,
|
||||
onBlur: this._handleFocusState,
|
||||
onChange: this._handleChange,
|
||||
onFocus: this._handleFocusState,
|
||||
ref: this._setCheckboxRef,
|
||||
style: [styles.nativeControl, styles.cursorInherit],
|
||||
type: 'checkbox'
|
||||
});
|
||||
|
||||
return (
|
||||
<View {...other} style={rootStyle}>
|
||||
<View style={trackStyle} />
|
||||
<View ref={this._setThumbRef} style={thumbStyle} />
|
||||
{nativeControl}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
_handleChange = (event: Object) => {
|
||||
const { onValueChange } = this.props;
|
||||
onValueChange && onValueChange(event.nativeEvent.target.checked);
|
||||
};
|
||||
|
||||
_handleFocusState = (event: Object) => {
|
||||
function handleFocusState(event: Object) {
|
||||
const isFocused = event.nativeEvent.type === 'focus';
|
||||
const boxShadow = isFocused ? thumbFocusedBoxShadow : thumbDefaultBoxShadow;
|
||||
if (this._thumbElement) {
|
||||
this._thumbElement.setNativeProps({ style: { boxShadow } });
|
||||
if (thumbRef.current != null) {
|
||||
thumbRef.current.setNativeProps({ style: { boxShadow } });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
_setCheckboxRef = element => {
|
||||
this._checkboxElement = element;
|
||||
};
|
||||
const { height: styleHeight, width: styleWidth } = StyleSheet.flatten(style);
|
||||
const height = styleHeight || 20;
|
||||
const minWidth = multiplyStyleLengthValue(height, 2);
|
||||
const width = styleWidth > minWidth ? styleWidth : minWidth;
|
||||
const trackBorderRadius = multiplyStyleLengthValue(height, 0.5);
|
||||
const trackCurrentColor = (function() {
|
||||
if (value === true) {
|
||||
if (trackColor != null && typeof trackColor === 'object') {
|
||||
return trackColor.true;
|
||||
} else {
|
||||
return activeTrackColor;
|
||||
}
|
||||
} else {
|
||||
if (trackColor != null && typeof trackColor === 'object') {
|
||||
return trackColor.false;
|
||||
} else {
|
||||
return trackColor;
|
||||
}
|
||||
}
|
||||
})();
|
||||
const thumbCurrentColor = value ? activeThumbColor : thumbColor;
|
||||
const thumbHeight = height;
|
||||
const thumbWidth = thumbHeight;
|
||||
|
||||
_setThumbRef = element => {
|
||||
this._thumbElement = element;
|
||||
};
|
||||
}
|
||||
const rootStyle = [styles.root, style, disabled && styles.cursorDefault, { height, width }];
|
||||
|
||||
const trackStyle = [
|
||||
styles.track,
|
||||
{
|
||||
backgroundColor: disabled ? '#D5D5D5' : trackCurrentColor,
|
||||
borderRadius: trackBorderRadius
|
||||
}
|
||||
];
|
||||
|
||||
const thumbStyle = [
|
||||
styles.thumb,
|
||||
value && styles.thumbActive,
|
||||
{
|
||||
backgroundColor: disabled ? '#BDBDBD' : thumbCurrentColor,
|
||||
height: thumbHeight,
|
||||
marginStart: value ? multiplyStyleLengthValue(thumbWidth, -1) : 0,
|
||||
width: thumbWidth
|
||||
}
|
||||
];
|
||||
|
||||
const nativeControl = createElement('input', {
|
||||
accessibilityLabel,
|
||||
checked: value,
|
||||
disabled: disabled,
|
||||
onBlur: handleFocusState,
|
||||
onChange: handleChange,
|
||||
onFocus: handleFocusState,
|
||||
ref: checkboxRef,
|
||||
style: [styles.nativeControl, styles.cursorInherit],
|
||||
type: 'checkbox'
|
||||
});
|
||||
|
||||
return (
|
||||
<View {...other} style={rootStyle}>
|
||||
<View style={trackStyle} />
|
||||
<View ref={thumbRef} style={thumbStyle} />
|
||||
{nativeControl}
|
||||
</View>
|
||||
);
|
||||
});
|
||||
|
||||
Switch.displayName = 'Switch';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
@@ -177,4 +185,4 @@ const styles = StyleSheet.create({
|
||||
}
|
||||
});
|
||||
|
||||
export default applyNativeMethods(Switch);
|
||||
export default Switch;
|
||||
|
||||
Reference in New Issue
Block a user