mirror of
https://github.com/zoriya/react-native-web.git
synced 2026-05-29 09:02:03 +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 */
|
/* eslint-env jasmine, jest */
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from 'enzyme';
|
import { render } from '@testing-library/react';
|
||||||
import Switch from '..';
|
import Switch from '..';
|
||||||
|
|
||||||
const checkboxSelector = 'input[type="checkbox"]';
|
function findCheckbox(container) {
|
||||||
|
return container.firstChild.querySelector('input');
|
||||||
|
}
|
||||||
|
|
||||||
describe('components/Switch', () => {
|
describe('components/Switch', () => {
|
||||||
test('accessibilityLabel is applied to native checkbox', () => {
|
test('accessibilityLabel is applied to native checkbox', () => {
|
||||||
const component = shallow(<Switch accessibilityLabel="switch" />);
|
const { container } = render(<Switch accessibilityLabel="switch" />);
|
||||||
expect(component.find(checkboxSelector).prop('aria-label')).toBe('switch');
|
expect(findCheckbox(container).getAttribute('aria-label')).toBe('switch');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('disabled', () => {
|
describe('disabled', () => {
|
||||||
test('when "false" a default checkbox is rendered', () => {
|
test('when "false" a default checkbox is rendered', () => {
|
||||||
const component = shallow(<Switch />);
|
const { container } = render(<Switch />);
|
||||||
expect(component.find(checkboxSelector).prop('disabled')).toBe(undefined);
|
expect(findCheckbox(container).disabled).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('when "true" a disabled checkbox is rendered', () => {
|
test('when "true" a disabled checkbox is rendered', () => {
|
||||||
const component = shallow(<Switch disabled />);
|
const { container } = render(<Switch disabled />);
|
||||||
expect(component.find(checkboxSelector).prop('disabled')).toBe(true);
|
expect(findCheckbox(container).disabled).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('onValueChange', () => {
|
describe('onValueChange', () => {
|
||||||
test('when value is "false" it receives "true"', () => {
|
test('when value is "false" it receives "true"', () => {
|
||||||
const onValueChange = jest.fn();
|
const onValueChange = jest.fn();
|
||||||
const component = shallow(<Switch onValueChange={onValueChange} value={false} />);
|
const { container } = render(<Switch onValueChange={onValueChange} value={false} />);
|
||||||
component.find('input').simulate('change', { nativeEvent: { target: { checked: true } } });
|
const checkbox = findCheckbox(container);
|
||||||
|
checkbox.click(); // Needed to get ReactDOM to trigger 'change' event
|
||||||
expect(onValueChange).toHaveBeenCalledWith(true);
|
expect(onValueChange).toHaveBeenCalledWith(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('when value is "true" it receives "false"', () => {
|
test('when value is "true" it receives "false"', () => {
|
||||||
const onValueChange = jest.fn();
|
const onValueChange = jest.fn();
|
||||||
const component = shallow(<Switch onValueChange={onValueChange} value />);
|
const { container } = render(<Switch onValueChange={onValueChange} value />);
|
||||||
component.find('input').simulate('change', { nativeEvent: { target: { checked: false } } });
|
const checkbox = findCheckbox(container);
|
||||||
|
checkbox.click(); // Needed to get ReactDOM to trigger 'change' event
|
||||||
expect(onValueChange).toHaveBeenCalledWith(false);
|
expect(onValueChange).toHaveBeenCalledWith(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('value', () => {
|
describe('value', () => {
|
||||||
test('when "false" an unchecked checkbox is rendered', () => {
|
test('when "false" an unchecked checkbox is rendered', () => {
|
||||||
const component = shallow(<Switch value={false} />);
|
const { container } = render(<Switch value={false} />);
|
||||||
expect(component.find(checkboxSelector).prop('checked')).toBe(false);
|
expect(findCheckbox(container).checked).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('when "true" a checked checkbox is rendered', () => {
|
test('when "true" a checked checkbox is rendered', () => {
|
||||||
const component = shallow(<Switch value />);
|
const { container } = render(<Switch value />);
|
||||||
expect(component.find(checkboxSelector).prop('checked')).toBe(true);
|
expect(findCheckbox(container).checked).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+106
-98
@@ -10,13 +10,12 @@
|
|||||||
import type { ColorValue } from '../../types';
|
import type { ColorValue } from '../../types';
|
||||||
import type { ViewProps } from '../View';
|
import type { ViewProps } from '../View';
|
||||||
|
|
||||||
import applyNativeMethods from '../../modules/applyNativeMethods';
|
|
||||||
import createElement from '../createElement';
|
import createElement from '../createElement';
|
||||||
import multiplyStyleLengthValue from '../../modules/multiplyStyleLengthValue';
|
import multiplyStyleLengthValue from '../../modules/multiplyStyleLengthValue';
|
||||||
import StyleSheet from '../StyleSheet';
|
import StyleSheet from '../StyleSheet';
|
||||||
import UIManager from '../UIManager';
|
import UIManager from '../UIManager';
|
||||||
import View from '../View';
|
import View from '../View';
|
||||||
import React from 'react';
|
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
|
||||||
|
|
||||||
type SwitchProps = {
|
type SwitchProps = {
|
||||||
...ViewProps,
|
...ViewProps,
|
||||||
@@ -33,110 +32,119 @@ const emptyObject = {};
|
|||||||
const thumbDefaultBoxShadow = '0px 1px 3px rgba(0,0,0,0.5)';
|
const thumbDefaultBoxShadow = '0px 1px 3px rgba(0,0,0,0.5)';
|
||||||
const thumbFocusedBoxShadow = `${thumbDefaultBoxShadow}, 0 0 0 10px rgba(0,0,0,0.1)`;
|
const thumbFocusedBoxShadow = `${thumbDefaultBoxShadow}, 0 0 0 10px rgba(0,0,0,0.1)`;
|
||||||
|
|
||||||
class Switch extends React.Component<SwitchProps> {
|
const Switch = forwardRef<SwitchProps, *>((props, ref) => {
|
||||||
_checkboxElement: HTMLInputElement;
|
const {
|
||||||
_thumbElement: View;
|
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() {
|
useImperativeHandle(
|
||||||
UIManager.blur(this._checkboxElement);
|
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() {
|
function handleFocusState(event: Object) {
|
||||||
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) => {
|
|
||||||
const isFocused = event.nativeEvent.type === 'focus';
|
const isFocused = event.nativeEvent.type === 'focus';
|
||||||
const boxShadow = isFocused ? thumbFocusedBoxShadow : thumbDefaultBoxShadow;
|
const boxShadow = isFocused ? thumbFocusedBoxShadow : thumbDefaultBoxShadow;
|
||||||
if (this._thumbElement) {
|
if (thumbRef.current != null) {
|
||||||
this._thumbElement.setNativeProps({ style: { boxShadow } });
|
thumbRef.current.setNativeProps({ style: { boxShadow } });
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
_setCheckboxRef = element => {
|
const { height: styleHeight, width: styleWidth } = StyleSheet.flatten(style);
|
||||||
this._checkboxElement = element;
|
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 => {
|
const rootStyle = [styles.root, style, disabled && styles.cursorDefault, { height, width }];
|
||||||
this._thumbElement = element;
|
|
||||||
};
|
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({
|
const styles = StyleSheet.create({
|
||||||
root: {
|
root: {
|
||||||
@@ -177,4 +185,4 @@ const styles = StyleSheet.create({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default applyNativeMethods(Switch);
|
export default Switch;
|
||||||
|
|||||||
Reference in New Issue
Block a user