Skip to content

Commit

Permalink
Merge pull request #345 from steniowagner/feat/famous-details
Browse files Browse the repository at this point in the history
feat: generating types for famous-details
  • Loading branch information
steniowagner authored Nov 10, 2023
2 parents 9a0ec8f + 8b08908 commit c6be750
Show file tree
Hide file tree
Showing 67 changed files with 3,232 additions and 17 deletions.
96 changes: 96 additions & 0 deletions __mocks__/famous-details.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { FAMOUS_DETAILS_QUERY } from '@/components/stacks/common-screens/famous-details/use-famous-details';
import { ISO6391Language } from '@/types/schema';
import { randomPositiveNumber } from './utils';
import { GraphQLError } from 'graphql';

const famous = {
knownForDepartment: 'KNOWN_FOR_DEPARTMENT',
placeOfBirth: 'PLACE_OF_BIRTH',
biography: 'BIOGRAPHY',
birthday: '1994-02-21',
deathday: '???',
images: Array(randomPositiveNumber(10, 1))
.fill('')
.map((_, index) => `IMAGE_${index}`),
cast: {
movies: Array(randomPositiveNumber(10, 1))
.fill({})
.map((_, index) => ({
voteAverage: randomPositiveNumber(10, 1),
posterPath: `POSTER_PATH_${index}`,
voteCount: randomPositiveNumber(10, 1),
title: `CAST_MOVIE_${index}`,
id: index,
})),
tvShows: Array(randomPositiveNumber(10, 1))
.fill({})
.map((_, index) => ({
voteAverage: randomPositiveNumber(10, 1),
posterPath: `POSTER_PATH_${index}`,
voteCount: randomPositiveNumber(10, 1),
title: `CAST_TV_SHOW_${index}`,
id: index,
})),
},
};

const baseMockFamousDetailsQueryResponse = (id: number) => {
const request = {
request: {
query: FAMOUS_DETAILS_QUERY,
variables: {
language: ISO6391Language.en,
id,
},
},
};
const result = {
result: {
data: {
famous,
},
},
};

const responseWithNetworkError = {
...request,
error: new Error('A Network error occurred'),
};

const responseWithGraphQLError = {
...request,
errors: [new GraphQLError('A GraphQL error occurred')],
};

return {
responseWithGraphQLError,
responseWithNetworkError,
request,
result,
};
};

export const mockQueryFamousDetailsSuccess = (id: number) => {
const query = baseMockFamousDetailsQueryResponse(id);
return [
{
...query.request,
...query.result,
},
];
};

export const mockQueryFamousDetailsError = (id: number) => {
const query = baseMockFamousDetailsQueryResponse(id);
const error = randomPositiveNumber(1) % 2 === 0 ? 'network' : 'graphql';
const errorResponse =
error === 'network'
? query.responseWithNetworkError
: query.responseWithGraphQLError;
return [
{
...query.request,
...errorResponse,
},
];
};
1 change: 1 addition & 0 deletions __mocks__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from './news';
export * from './quiz-questions';
export * from './trending-famous';
export * from './search';
export * from './famous-details';
10 changes: 10 additions & 0 deletions src/components/common/images-list/ImagesList.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ScrollView } from 'react-native';
import styled from 'styled-components/native';

export const Wrapper = styled(ScrollView).attrs(({ theme }) => ({
contentContainerStyle: {
paddingLeft: theme.metrics.md,
},
}))`
margin-bottom: ${({ theme }) => theme.metrics.xl * 2}px;
`;
211 changes: 211 additions & 0 deletions src/components/common/images-list/ImagesList.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
import React from 'react';
import {
RenderAPI,
fireEvent,
render,
waitFor,
} from '@testing-library/react-native';
import { ThemeProvider } from 'styled-components/native';

import { borderRadius } from '@/styles/border-radius';
import { dark as theme } from '@styles/themes';
import metrics from '@/styles/metrics';
import { Routes } from '@/navigation';

import { Orientation } from './images-list-item/ImageListItem.styles';
import { randomPositiveNumber } from '../../../../__mocks__/utils';
import { ImagesList } from './ImagesList';

const images = Array(randomPositiveNumber(10, 1))
.fill('')
.map((_, index) => `/image_${index}`);

const mockNavigate = jest.fn();
const mockGetState = jest.fn();

jest.mock('@react-navigation/native', () => {
const actualReactNavigationNative = jest.requireActual(
'@react-navigation/native',
);
return {
...actualReactNavigationNative,
useNavigation: () => ({
navigate: mockNavigate,
getState: mockGetState,
}),
};
});

const renderImages = (orientation: Orientation, images: string[]) => (
<ThemeProvider theme={theme}>
<ImagesList orientation={orientation} images={images} />
</ThemeProvider>
);

describe('Components/Common/ImagesList', () => {
const elements = {
list: (component: RenderAPI) => component.queryByTestId('images-list'),
listItems: (component: RenderAPI) =>
component.queryAllByTestId('image-list-item-button'),
images: (component: RenderAPI) =>
component.queryAllByTestId('tmdb-fallback-image'),
};

describe('When "orientation" is "PORTRAIT"', () => {
const orientation: Orientation = 'PORTRAIT';

describe('Rendering', () => {
it('should render correctly when "has some items to render"', () => {
const component = render(renderImages(orientation, images));
expect(elements.list(component)).not.toBeNull();
expect(elements.listItems(component).length).toEqual(images.length);
});

it('should render correctly when "has no items to render"', () => {
const component = render(renderImages(orientation, []));
expect(elements.list(component)).toBeNull();
});

it('should render with the correct styles', async () => {
const component = render(renderImages(orientation, images));
for (let i = 0; i < elements.images(component).length; i++) {
expect(elements.images(component)[i].props.style.width).toEqual(
metrics.getWidthFromDP('36'),
);
expect(elements.images(component)[i].props.style.height).toEqual(
metrics.getWidthFromDP('44'),
);
expect(
elements.images(component)[i].props.style.borderRadius,
).toEqual(borderRadius.xs);
}
await waitFor(() => {});
});
});

describe('Pressing list-items', () => {
describe('When the "current-stack" is "Home"', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('shoudl call "navigation.navigate" correctly', () => {
const indexImageSelected = randomPositiveNumber(images.length - 1);
const component = render(renderImages(orientation, images));
mockGetState.mockReturnValue({
routes: [
{
name: Routes.Home.IMAGES_GALLERY,
},
],
});
expect(mockNavigate).toBeCalledTimes(0);
fireEvent.press(elements.listItems(component)[indexImageSelected]);
expect(mockNavigate).toBeCalledTimes(1);
expect(mockNavigate).toBeCalledWith(Routes.Home.IMAGES_GALLERY);
});
});

describe('When the "current-stack" is "Famous"', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('shoudl call "navigation.navigate" correctly', () => {
const indexImageSelected = randomPositiveNumber(images.length - 1);
const component = render(renderImages(orientation, images));
mockGetState.mockReturnValue({
routes: [
{
name: Routes.Famous.DETAILS,
},
],
});
expect(mockNavigate).toBeCalledTimes(0);
fireEvent.press(elements.listItems(component)[indexImageSelected]);
expect(mockNavigate).toBeCalledTimes(1);
expect(mockNavigate).toBeCalledWith(Routes.Famous.IMAGES_GALLERY);
});
});
});
});

describe('When "orientation" is "LANDSCAPE"', () => {
const orientation: Orientation = 'LANDSCAPE';

describe('Rendering', () => {
it('should render correctly when "has some items to render"', () => {
const component = render(renderImages(orientation, images));
expect(elements.list(component)).not.toBeNull();
expect(elements.listItems(component).length).toEqual(images.length);
});

it('should render correctly when "has no items to render"', () => {
const component = render(renderImages(orientation, []));
expect(elements.list(component)).toBeNull();
});

it('should render with the correct styles', async () => {
const component = render(renderImages(orientation, images));
for (let i = 0; i < elements.images(component).length; i++) {
expect(elements.images(component)[i].props.style.width).toEqual(
metrics.getWidthFromDP('60'),
);
expect(elements.images(component)[i].props.style.height).toEqual(
metrics.getWidthFromDP('36'),
);
expect(
elements.images(component)[i].props.style.borderRadius,
).toEqual(borderRadius.xs);
}
await waitFor(() => {});
});
});

describe('Pressing list-items', () => {
describe('When the "current-stack" is "Home"', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('shoudl call "navigation.navigate" correctly', () => {
const indexImageSelected = randomPositiveNumber(images.length - 1);
const component = render(renderImages(orientation, images));
mockGetState.mockReturnValue({
routes: [
{
name: Routes.Home.IMAGES_GALLERY,
},
],
});
expect(mockNavigate).toBeCalledTimes(0);
fireEvent.press(elements.listItems(component)[indexImageSelected]);
expect(mockNavigate).toBeCalledTimes(1);
expect(mockNavigate).toBeCalledWith(Routes.Home.IMAGES_GALLERY);
});
});

describe('When the "current-stack" is "Famous"', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('shoudl call "navigation.navigate" correctly', () => {
const indexImageSelected = randomPositiveNumber(images.length - 1);
const component = render(renderImages(orientation, images));
mockGetState.mockReturnValue({
routes: [
{
name: Routes.Famous.DETAILS,
},
],
});
expect(mockNavigate).toBeCalledTimes(0);
fireEvent.press(elements.listItems(component)[indexImageSelected]);
expect(mockNavigate).toBeCalledTimes(1);
expect(mockNavigate).toBeCalledWith(Routes.Famous.IMAGES_GALLERY);
});
});
});
});
});
34 changes: 34 additions & 0 deletions src/components/common/images-list/ImagesList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';

import { ImageOrientation } from './images-list-item/ImageListItem.styles';
import { ImageListItem } from './images-list-item/ImageListItem';
import * as Styles from './ImagesList.styles';
import { useImagesList } from './use-images-list';

type ImagesListProps = ImageOrientation & {
images: string[];
};

export const ImagesList = (props: ImagesListProps) => {
const imagesList = useImagesList({ images: props.images });

if (!props.images.length) {
return null;
}

return (
<Styles.Wrapper
showsHorizontalScrollIndicator={false}
testID="images-list"
horizontal>
{props.images.map((image, index) => (
<ImageListItem
onPress={() => imagesList.onPressImage(index)}
orientation={props.orientation}
image={image}
key={image}
/>
))}
</Styles.Wrapper>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { StyleSheet } from 'react-native';
import styled from 'styled-components/native';

import { borderRadius } from '@styles/border-radius';
import metrics from '@/styles/metrics';

export const DEFAULT_ICON_SIZE = metrics.xl * 2;

export type Orientation = 'PORTRAIT' | 'LANDSCAPE';

export type ImageOrientation = {
orientation: Orientation;
};

export const Wrapper = styled.TouchableOpacity`
margin-right: ${({ theme }) => theme.metrics.md}px;
`;

export const makeImageStyle = (orientation: Orientation) => {
const width = orientation === 'PORTRAIT' ? '36' : '60';
const height = orientation === 'PORTRAIT' ? '44' : '36';
return StyleSheet.create({
image: {
width: metrics.getWidthFromDP(width),
height: metrics.getWidthFromDP(height),
borderRadius: borderRadius.xs,
},
});
};
Loading

0 comments on commit c6be750

Please sign in to comment.