Skip to content

Commit

Permalink
BREAKING: refactor Toggle to new api
Browse files Browse the repository at this point in the history
  • Loading branch information
Artur Yorsh committed Feb 28, 2020
1 parent da7d51f commit c0b5f1e
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 131 deletions.
100 changes: 41 additions & 59 deletions src/components/ui/toggle/toggle.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,39 +14,38 @@ import {
PanResponderGestureState,
PanResponderInstance,
Platform,
StyleProp,
StyleSheet,
TextStyle,
TouchableOpacity,
TouchableOpacityProps,
View,
ViewProps,
} from 'react-native';
import { Overwrite } from 'utility-types';
import {
FalsyText,
RenderProp,
RTLService,
TouchableWithoutFeedback,
WebEventResponder,
WebEventResponderInstance,
} from '../../devsupport';
import {
Interaction,
styled,
StyledComponentProps,
StyleType,
} from '@kitten/theme';
import {
Text,
TextElement,
} from '../text/text.component';
import { CheckMark } from '../support/components/checkmark.component';
import {
I18nLayoutService,
WebEventResponder,
WebEventResponderInstance,
} from '../support/services';
} from '../../theme';
import { TextProps } from '../text/text.component';
import { CheckMark } from '../shared/checkmark.component';

export interface ToggleProps extends StyledComponentProps, TouchableOpacityProps {
type ToggleStyledProps = Overwrite<StyledComponentProps, {
appearance?: 'default' | string;
}>;

export interface ToggleProps extends TouchableOpacityProps, ToggleStyledProps {
checked?: boolean;
disabled?: boolean;
status?: string;
size?: string;
text?: string;
textStyle?: StyleProp<TextStyle>;
onChange?: (checked: boolean) => void;
text?: RenderProp<TextProps> | React.ReactText;
status?: 'basic' | 'primary' | 'success' | 'info' | 'warning' | 'danger' | 'control' | string;
}

export type ToggleElement = React.ReactElement<ToggleProps>;
Expand All @@ -59,18 +58,16 @@ export type ToggleElement = React.ReactElement<ToggleProps>;
* @property {boolean} checked - Determines whether component is checked.
* Default is `false`.
*
* @property {boolean} disabled - Determines whether component is disabled.
* Default is `false.
*
* @property {string} status - Determines the status of the component.
* Can be `basic`, `primary`, `success`, `info`, `warning`, `danger` or `control`.
* Default is `basic`.
*
* @property {string} text - Determines text of the component.
* @property {string | (props: TextProps) => ReactElement} text - A string or a function component
* to render near the toggle.
* If it is a function, it will be called with props provided by Eva.
* Otherwise, renders a Text styled by Eva.
*
* @property {StyleProp<TextStyle>} textStyle - Customizes text style.
*
* @property {(checked: boolean) => void} onChange - Fires when selection state is changed.
* @property {(checked: boolean) => void} onChange - Called on toggle value change.
*
* @property {TouchableOpacityProps} ...TouchableOpacityProps - Any props applied to TouchableOpacity component.
*
Expand Down Expand Up @@ -204,7 +201,7 @@ export class ToggleComponent extends React.Component<ToggleProps> implements Pan
}
};

private getComponentStyle = (source: StyleType): StyleType => {
private getComponentStyle = (source: StyleType) => {
const { checked, disabled } = this.props;

const {
Expand All @@ -231,7 +228,6 @@ export class ToggleComponent extends React.Component<ToggleProps> implements Pan
} = source;

return {
toggleContainer: {},
ellipseContainer: {
borderColor: borderColor,
backgroundColor: backgroundColor,
Expand Down Expand Up @@ -280,7 +276,7 @@ export class ToggleComponent extends React.Component<ToggleProps> implements Pan
this.thumbTranslateAnimationActive = true;

Animated.timing(this.thumbTranslateAnimation, {
toValue: I18nLayoutService.select(value, -value),
toValue: RTLService.select(value, -value),
duration: 150,
easing: Easing.linear,
}).start(() => {
Expand Down Expand Up @@ -326,44 +322,30 @@ export class ToggleComponent extends React.Component<ToggleProps> implements Pan
this.animateThumbWidth(this.props.eva.style.thumbWidth);
};

private renderTextElement = (style: StyleType): TextElement => {
return (
<Text style={[style, this.props.textStyle]}>
{this.props.text}
</Text>
);
};

private renderComponentChildren = (style: StyleType): React.ReactNodeArray => {
return [
this.props.text && this.renderTextElement(style.text),
];
};

public render(): React.ReactElement<ViewProps> {
const { eva, style, checked, ...restProps } = this.props;

const componentStyle: StyleType = this.getComponentStyle(eva.style);
const [textElement] = this.renderComponentChildren(componentStyle);
const { eva, style, checked, text, ...touchableProps } = this.props;
const evaStyle = this.getComponentStyle(eva.style);

return (
<View
{...this.panResponder.panHandlers}
style={[styles.container, style]}>
<TouchableOpacity
activeOpacity={1.0}
{...restProps}
<TouchableWithoutFeedback
{...touchableProps}
{...this.webEventResponder.eventHandlers}
style={[componentStyle.toggleContainer, webStyles.toggleContainer, styles.toggleContainer]}>
<View style={[componentStyle.highlight, styles.highlight]}/>
<Animated.View style={[componentStyle.ellipseContainer, styles.ellipseContainer]}>
<Animated.View style={[componentStyle.ellipse, styles.ellipse]}/>
<Animated.View style={[componentStyle.thumb, styles.thumb]}>
<CheckMark {...componentStyle.icon} />
style={[webStyles.toggleContainer, styles.toggleContainer]}>
<View style={[evaStyle.highlight, styles.highlight]}/>
<Animated.View style={[evaStyle.ellipseContainer, styles.ellipseContainer]}>
<Animated.View style={[evaStyle.ellipse, styles.ellipse]}/>
<Animated.View style={[evaStyle.thumb, styles.thumb]}>
<CheckMark {...evaStyle.icon} />
</Animated.View>
</Animated.View>
</TouchableOpacity>
{textElement}
</TouchableWithoutFeedback>
<FalsyText
style={evaStyle.text}
component={text}
/>
</View>
);
}
Expand Down
122 changes: 50 additions & 72 deletions src/components/ui/toggle/toggle.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,110 +1,88 @@
/**
* @license
* Copyright Akveo. All Rights Reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*/

import React from 'react';
import { Text } from 'react-native';
import {
fireEvent,
render,
RenderAPI,
waitForElement,
} from 'react-native-testing-library';
import { ReactTestInstance } from 'react-test-renderer';
import {
ApplicationProvider,
ApplicationProviderProps,
} from '@kitten/theme';
light,
mapping,
} from '@eva-design/eva';
import { ApplicationProvider } from '../../theme';
import {
Toggle,
ToggleComponent,
ToggleProps,
} from './toggle.component';
import {
mapping,
theme,
} from '../support/tests';

jest.mock('react-native/Libraries/Animated/src/Animated', (): unknown => {
const AnimatedModule = jest.requireActual('react-native/Libraries/Animated/src/Animated');
return {
...AnimatedModule,
timing: (value, config) => {
return {
start: (callback) => {
value.setValue(config.toValue);
callback && callback();
},
};
},
};
});

const Mock = (props?: ToggleProps): React.ReactElement<ApplicationProviderProps> => {
return (
describe('@toggle: component checks', () => {

const TestToggle = (props?: ToggleProps) => (
<ApplicationProvider
mapping={mapping}
theme={theme}>
theme={light}>
<Toggle {...props} />
</ApplicationProvider>
);
};

const renderComponent = (props?: ToggleProps): RenderAPI => {
return render(
<Mock {...props}/>,
);
};
it('should request checking', async () => {
const onCheckedChange = jest.fn();

describe('@toggle: component checks', () => {
const component = render(
<TestToggle
checked={false}
onChange={onCheckedChange}
/>,
);

it('contains text', () => {
const component: RenderAPI = renderComponent({
text: 'Sample Text',
});
const responder = component.getByType(ToggleComponent).children[0];
fireEvent(responder as ReactTestInstance, 'responderRelease');

expect(component.getByText('Sample Text')).toBeTruthy();
await waitForElement(() => expect(onCheckedChange).toBeCalledWith(true));
});

it('emits onChange', () => {
const onChange = jest.fn();
it('should request unchecking', async () => {
const onCheckedChange = jest.fn();

const component: RenderAPI = renderComponent({ onChange });
const { [0]: containerView } = component.getByType(ToggleComponent).children;
const component = render(
<TestToggle
checked={true}
onChange={onCheckedChange}
/>,
);

fireEvent(containerView as ReactTestInstance, 'responderRelease');
const responder = component.getByType(ToggleComponent).children[0];
fireEvent(responder as ReactTestInstance, 'responderRelease');

expect(onChange).toHaveBeenCalled();
await waitForElement(() => expect(onCheckedChange).toBeCalledWith(false));
});

it('checking of value direct', () => {
let checked: boolean = false;
const onChangeValue = (changed: boolean) => {
checked = changed;
};

const component: RenderAPI = renderComponent({
checked: checked,
onChange: onChangeValue,
});
it('should render text', () => {
const component = render(
<TestToggle text='I love Babel'/>,
);

const { [0]: containerView } = component.getByType(ToggleComponent).children;
const text = component.getByText('I love Babel');

fireEvent(containerView as ReactTestInstance, 'responderRelease');

expect(checked).toBe(true);
expect(text).toBeTruthy();
});

it('checking of value reverse', () => {
let checked: boolean = true;
const onChangeValue = (changed: boolean) => {
checked = changed;
};

const component: RenderAPI = renderComponent({
checked: checked,
onChange: onChangeValue,
});

const { [0]: containerView } = component.getByType(ToggleComponent).children;
it('should render text as component', () => {
const component = render(
<TestToggle text={props => <Text {...props}>I love Babel</Text>}/>,
);

fireEvent(containerView as ReactTestInstance, 'responderRelease');
const text = component.getByText('I love Babel');

expect(checked).toBe(false);
expect(text).toBeTruthy();
});

});

0 comments on commit c0b5f1e

Please sign in to comment.