Skip to content

Commit

Permalink
BREAKING: refactor Drawer 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 cc4ea57 commit 9188551
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 183 deletions.
104 changes: 57 additions & 47 deletions src/components/ui/drawer/drawer.component.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,43 @@
/**
* @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 { ViewProps } from 'react-native';
import { Overwrite } from 'utility-types';
import {
FalsyFC,
RenderProp,
} from '../../devsupport';
import {
styled,
StyledComponentProps,
} from '@kitten/theme';
StyleType,
} from '../../theme';
import {
Menu,
MenuElement,
MenuProps,
} from '../menu/menu.component';
import { MenuItemType } from '../menu/menuItem.component';

export type DrawerHeaderElement = React.ReactElement;
export type DrawerFooterElement = React.ReactElement;
type DrawerStyledProps = Overwrite<StyledComponentProps, {
appearance?: 'default' | 'noDivider' | string;
}>;

export interface DrawerProps extends StyledComponentProps, MenuProps {
header?: () => DrawerHeaderElement;
footer?: () => DrawerFooterElement;
export interface DrawerProps extends MenuProps, DrawerStyledProps {
header?: RenderProp<ViewProps>;
footer?: RenderProp<ViewProps>;
}

export type DrawerElement = React.ReactElement<DrawerProps>;

/**
* Styled `Navigation Drawer` component. The principle of rendering a `Drawer` is the same as a rendering a List.
* Styled `Drawer` component.
* Renders a Menu with additional styles provided by Eva.
*
* @extends React.Component
*
* @property {MenuItemType[]} data - Determines the items displayed in drawer menu.
*
* @property {string} appearance - Determines the appearance of the component.
* Can be `default` or `noDivider`.
* Default is `default`.
*
* @property {() => ReactElement} header - Determines the function to render a header. Optional.
*
* @property {() => ReactElement} footer - Determines the function to render a footer. Optional.
Expand Down Expand Up @@ -107,45 +114,48 @@ class DrawerComponent extends React.Component<DrawerProps> {

static styledComponentName: string = 'Drawer';

private renderHeader = (): DrawerHeaderElement => {
return this.props.header();
};

private renderFooter = (): DrawerFooterElement => {
return this.props.footer();
};

private renderMenu = (): MenuElement => {
const { style, header, footer, eva, ...restProps } = this.props;

return (
<Menu
style={eva.style}
showsVerticalScrollIndicator={false}
bounces={false}
{...restProps}
/>
);
};

private renderComponentChildren = (): React.ReactNodeArray => {
const { header, footer } = this.props;
private getComponentStyle = (source: StyleType) => {
const {
headerPaddingHorizontal,
headerPaddingVertical,
footerPaddingHorizontal,
footerPaddingVertical,
...containerParameters
} = source;

return [
header && this.renderHeader(),
this.renderMenu(),
footer && this.renderFooter(),
];
return {
container: containerParameters,
header: {
paddingHorizontal: headerPaddingHorizontal,
paddingVertical: headerPaddingVertical,
},
footer: {
paddingHorizontal: footerPaddingHorizontal,
paddingVertical: footerPaddingVertical,
},
};
};

public render(): React.ReactFragment {
const [header, menu, footer] = this.renderComponentChildren();
const { eva, style, header, footer, ...menuProps } = this.props;
const evaStyle = this.getComponentStyle(eva.style);

return (
<React.Fragment>
{header}
{menu}
{footer}
<FalsyFC
style={evaStyle.header}
component={header}
/>
<Menu
style={[evaStyle.container, style]}
showsVerticalScrollIndicator={false}
bounces={false}
{...menuProps}
/>
<FalsyFC
style={evaStyle.footer}
component={footer}
/>
</React.Fragment>
);
}
Expand Down
177 changes: 93 additions & 84 deletions src/components/ui/drawer/drawer.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,136 +1,145 @@
/**
* @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 {
Image,
ImageSourcePropType,
ImageProps,
Text,
TouchableOpacity,
} from 'react-native';
import {
fireEvent,
render,
RenderAPI,
} from 'react-native-testing-library';
import {
ApplicationProvider,
ApplicationProviderProps,
} from '@kitten/theme';
light,
mapping,
} from '@eva-design/eva';
import { ApplicationProvider } from '../../theme';
import {
Drawer,
DrawerProps,
} from '../drawer/drawer.component';
import { DrawerHeaderFooter } from './drawerHeaderFooter.component';
} from './drawer.component';
import {
MenuItemType,
MenuItem,
} from '../menu/menuItem.component';
import {
mapping,
theme,
} from '../support/tests';
DrawerItem,
DrawerItemProps,
} from './drawerItem.component';

const data: MenuItemType[] = [
{ title: 'Item 1' },
{ title: 'Item 2' },
{ title: 'Item 3' },
];
describe('@drawer-item: component checks', () => {

const Mock = (props?: DrawerProps): React.ReactElement<ApplicationProviderProps> => {
return (
const TestDrawerItem = (props?: DrawerItemProps) => (
<ApplicationProvider
mapping={mapping}
theme={theme}>
<Drawer data={data} {...props} />
theme={light}>
<DrawerItem {...props}/>
</ApplicationProvider>
);
};

const renderComponent = (props?: DrawerProps): RenderAPI => {
return render(
<Mock {...props}/>,
);
};

describe('@drawer: component checks', () => {

it('* should render proper number of items', () => {
const component: RenderAPI = renderComponent();
it('should render text passed to title prop', () => {
const component = render(
<TestDrawerItem title='I love Babel'/>,
);

expect(component.getAllByType(MenuItem).length).toEqual(3);
const title = component.getByText('I love Babel');
expect(title).toBeTruthy();
});

it('* item should render title', () => {
const component: RenderAPI = renderComponent();
it('should render component passed to title prop', () => {
const component = render(
<TestDrawerItem title={props => <Text {...props}>I love Babel</Text>}/>,
);

expect(component.getByText('Item 1')).toBeTruthy();
expect(component.getByText('Item 2')).toBeTruthy();
expect(component.getByText('Item 3')).toBeTruthy();
const titleAsComponent = component.getByText('I love Babel');
expect(titleAsComponent).toBeTruthy();
});

it('* item should render icon', () => {
const source: ImageSourcePropType = { uri: 'https://akveo.github.io/eva-icons/fill/png/128/star.png' };
it('should render components passed to accessoryLeft or accessoryRight props', () => {
const AccessoryLeft = (props): React.ReactElement<ImageProps> => (
<Image
{...props}
source={{ uri: 'https://akveo.github.io/eva-icons/fill/png/128/star.png' }}
/>
);

const icon = () => (
<Image testID='@drawer-item-icon' source={source}/>
const AccessoryRight = (props): React.ReactElement<ImageProps> => (
<Image
{...props}
source={{ uri: 'https://akveo.github.io/eva-icons/fill/png/128/home.png' }}
/>
);

const drawerData: MenuItemType[] = [
{ title: 'Item 1', icon },
{ title: 'Item 2', icon },
{ title: 'Item 3', icon },
];
const component = render(
<TestDrawerItem
accessoryLeft={AccessoryLeft}
accessoryRight={AccessoryRight}
/>,
);

const [accessoryLeft, accessoryRight] = component.getAllByType(Image);

const component: RenderAPI = renderComponent({ data: drawerData, onSelect: () => 1 });
expect(accessoryLeft).toBeTruthy();
expect(accessoryRight).toBeTruthy();

expect(component.getAllByTestId('@drawer-item-icon').length).toEqual(3);
expect(accessoryLeft.props.source.uri).toEqual('https://akveo.github.io/eva-icons/fill/png/128/star.png');
expect(accessoryRight.props.source.uri).toEqual('https://akveo.github.io/eva-icons/fill/png/128/home.png');
});

it('* item should render accessory view', () => {
const source: ImageSourcePropType = { uri: 'https://akveo.github.io/eva-icons/fill/png/128/star.png' };
it('should call onPress', () => {
const onPress = jest.fn();

const accessory = () => (
<Image testID='@drawer-item-accessory' source={source}/>
const component = render(
<TestDrawerItem onPress={onPress}/>,
);

const drawerData: MenuItemType[] = [
{ title: 'Item 1', accessory },
{ title: 'Item 2', accessory },
{ title: 'Item 3', accessory },
];

const component: RenderAPI = renderComponent({ data: drawerData, onSelect: () => 1 });
const touchable = component.getByType(TouchableOpacity);
fireEvent.press(touchable);

expect(component.getAllByTestId('@drawer-item-accessory').length).toEqual(3);
expect(onPress).toHaveBeenCalled();
});
});

it('* should render header', () => {
const header = () => (
<DrawerHeaderFooter testID='@drawer-header'/>
);

const component: RenderAPI = renderComponent({ data, header, onSelect: () => 1 });
describe('@drawer: component checks', () => {

expect(component.getAllByTestId('@drawer-header').length).toBeTruthy();
});
const TestDrawer = (props?: DrawerProps) => (
<ApplicationProvider
mapping={mapping}
theme={light}>
<Drawer {...props}>
<DrawerItem/>
<DrawerItem/>
</Drawer>
</ApplicationProvider>
);

it('* should render footer', () => {
const footer = () => (
<DrawerHeaderFooter testID='@drawer-footer'/>
it('should render 2 drawer items passed to children', () => {
const component = render(
<TestDrawer/>,
);

const component: RenderAPI = renderComponent({ data, footer, onSelect: () => 1 });

expect(component.getAllByTestId('@drawer-footer').length).toBeTruthy();
const items = component.getAllByType(DrawerItem);
expect(items.length).toEqual(2);
});

it('* should call onSelect', () => {
const pressIndex: number = 1;
it('should render component passed to header prop', () => {
const component = render(
<TestDrawer header={() => <Text>I love Babel</Text>}/>,
);

const onSelect = jest.fn((index: number) => {
expect(index).toEqual(pressIndex);
});
const header = component.getByText('I love Babel');
expect(header).toBeTruthy();
});

const component: RenderAPI = renderComponent({ data, onSelect });
it('should render component passed to footer prop', () => {
const component = render(
<TestDrawer footer={() => <Text>I love Babel</Text>}/>,
);

fireEvent.press(component.getAllByType(TouchableOpacity)[pressIndex]);
const footer = component.getByText('I love Babel');
expect(footer).toBeTruthy();
});

});
Loading

0 comments on commit 9188551

Please sign in to comment.