From c4dfe41f28cdefb7584d5ee31be148decaeee668 Mon Sep 17 00:00:00 2001 From: Steffen Kleinle Date: Mon, 30 May 2022 17:20:02 +0200 Subject: [PATCH] LUN-357: Use own vocabulary detail route instead of modal --- src/components/VocabularyList.tsx | 26 +---- src/components/VocabularyListItem.tsx | 8 +- src/components/VocabularyListModal.tsx | 101 ------------------ .../__tests__/VocabularyList.spec.tsx | 55 ++++++++++ .../__tests__/VocabularyListItem.spec.tsx | 17 ++- .../__tests__/VocabularyListModal.spec.tsx | 51 --------- src/navigation/NavigationTypes.ts | 9 ++ src/navigation/Navigator.tsx | 6 ++ src/routes/ResultDetailScreen.tsx | 7 +- src/routes/VocabularyDetailScreen.tsx | 62 +++++++++++ src/routes/VocabularyListScreen.tsx | 8 +- .../__tests__/VocabularyListScreen.spec.tsx | 65 ----------- src/routes/home/HomeScreen.tsx | 12 ++- src/routes/home/components/DisciplineCard.tsx | 7 +- .../__tests__/DisciplineCard.spec.tsx | 17 ++- 15 files changed, 191 insertions(+), 260 deletions(-) delete mode 100644 src/components/VocabularyListModal.tsx create mode 100644 src/components/__tests__/VocabularyList.spec.tsx delete mode 100644 src/components/__tests__/VocabularyListModal.spec.tsx create mode 100644 src/routes/VocabularyDetailScreen.tsx delete mode 100644 src/routes/__tests__/VocabularyListScreen.spec.tsx diff --git a/src/components/VocabularyList.tsx b/src/components/VocabularyList.tsx index 71d458dab..de7cefa6d 100644 --- a/src/components/VocabularyList.tsx +++ b/src/components/VocabularyList.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react' +import React from 'react' import { FlatList } from 'react-native' import styled from 'styled-components/native' @@ -6,7 +6,6 @@ import { Document } from '../constants/endpoints' import labels from '../constants/labels.json' import Title from './Title' import VocabularyListItem from './VocabularyListItem' -import VocabularyListModal from './VocabularyListModal' const Root = styled.View` background-color: ${props => props.theme.colors.background}; @@ -17,33 +16,16 @@ const Root = styled.View` interface VocabularyListScreenProps { documents: Document[] + onItemPress: (index: number) => void } -const VocabularyList = ({ documents }: VocabularyListScreenProps): JSX.Element => { - const [isModalVisible, setIsModalVisible] = useState(false) - const [selectedDocumentIndex, setSelectedDocumentIndex] = useState(0) - +const VocabularyList = ({ documents, onItemPress }: VocabularyListScreenProps): JSX.Element => { const renderItem = ({ item, index }: { item: Document; index: number }): JSX.Element => ( - { - setIsModalVisible(true) - setSelectedDocumentIndex(index) - }} - /> + onItemPress(index)} /> ) return ( - {documents[selectedDocumentIndex] && ( - - )} void + onPress: () => void } -const VocabularyListItem = ({ document, setIsModalVisible }: VocabularyListItemProp): ReactElement => { +const VocabularyListItem = ({ document, onPress }: VocabularyListItemProp): ReactElement => { const { article, word, document_image: documentImage } = document const title = <StyledTitle articleColor={getArticleColor(article)}>{article.value}</StyledTitle> @@ -43,13 +43,11 @@ const VocabularyListItem = ({ document, setIsModalVisible }: VocabularyListItemP <StyledImage testID='image' source={{ uri: documentImage[0].image }} width={24} height={24} /> ) : undefined - const noop = () => undefined - return ( <ListItem title={title} description={word} - onPress={setIsModalVisible ?? noop} + onPress={onPress} icon={icon} rightChildren={ <Speaker> diff --git a/src/components/VocabularyListModal.tsx b/src/components/VocabularyListModal.tsx deleted file mode 100644 index c27b7e8d0..000000000 --- a/src/components/VocabularyListModal.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import React, { ReactElement } from 'react' -import { Modal, SafeAreaView } from 'react-native' -import { widthPercentageToDP as wp } from 'react-native-responsive-screen' -import styled from 'styled-components/native' - -import { CloseCircleIconWhite, ArrowRightIcon } from '../../assets/images' -import { BUTTONS_THEME } from '../constants/data' -import { Document } from '../constants/endpoints' -import labels from '../constants/labels.json' -import AudioPlayer from './AudioPlayer' -import Button from './Button' -import ImageCarousel from './ImageCarousel' -import WordItem from './WordItem' - -const ModalContainer = styled.View` - background-color: ${props => props.theme.colors.background}; - flex: 1; -` - -const ModalHeader = styled.View` - display: flex; - align-items: flex-end; - padding: ${props => props.theme.spacings.xs}; - border-bottom-color: ${props => props.theme.colors.disabled}; - border-bottom-width: 1px; - margin-bottom: ${props => props.theme.spacings.xs}; -` - -const ItemContainer = styled.View` - margin: ${props => props.theme.spacings.xl} 0; - height: 10%; - width: 85%; - align-self: center; -` - -const ButtonContainer = styled.View` - display: flex; - align-self: center; -` - -interface VocabularyListModalProps { - documents: Document[] - isModalVisible: boolean - setIsModalVisible: (isModalVisible: boolean) => void - selectedDocumentIndex: number - setSelectedDocumentIndex: (selectedDocumentIndex: number) => void -} - -const VocabularyListModal = ({ - documents, - isModalVisible, - setIsModalVisible, - selectedDocumentIndex, - setSelectedDocumentIndex -}: VocabularyListModalProps): ReactElement => { - const goToNextWord = (): void => { - if (selectedDocumentIndex + 1 < documents.length) { - setSelectedDocumentIndex(selectedDocumentIndex + 1) - } - } - - return ( - <Modal animationType='slide' transparent visible={isModalVisible} onRequestClose={() => setIsModalVisible(false)}> - <ModalContainer> - <SafeAreaView> - <ModalHeader> - <CloseCircleIconWhite onPress={() => setIsModalVisible(false)} width={wp('7%')} height={wp('7%')} /> - </ModalHeader> - <ImageCarousel images={documents[selectedDocumentIndex].document_image} /> - <AudioPlayer document={documents[selectedDocumentIndex]} disabled={false} /> - <ItemContainer> - <WordItem - answer={{ - word: documents[selectedDocumentIndex].word, - article: documents[selectedDocumentIndex].article - }} - /> - </ItemContainer> - <ButtonContainer> - {documents.length > selectedDocumentIndex + 1 ? ( - <Button - label={labels.exercises.next} - iconRight={ArrowRightIcon} - onPress={goToNextWord} - buttonTheme={BUTTONS_THEME.contained} - /> - ) : ( - <Button - label={labels.general.header.cancelExercise} - onPress={() => setIsModalVisible(false)} - buttonTheme={BUTTONS_THEME.contained} - /> - )} - </ButtonContainer> - </SafeAreaView> - </ModalContainer> - </Modal> - ) -} - -export default VocabularyListModal diff --git a/src/components/__tests__/VocabularyList.spec.tsx b/src/components/__tests__/VocabularyList.spec.tsx new file mode 100644 index 000000000..04affd344 --- /dev/null +++ b/src/components/__tests__/VocabularyList.spec.tsx @@ -0,0 +1,55 @@ +import { fireEvent } from '@testing-library/react-native' +import React from 'react' + +import labels from '../../constants/labels.json' +import DocumentBuilder from '../../testing/DocumentBuilder' +import { mockUseLoadAsyncWithData } from '../../testing/mockUseLoadFromEndpoint' +import render from '../../testing/render' +import VocabularyList from '../VocabularyList' + +jest.mock('../AudioPlayer', () => { + const Text = require('react-native').Text + return () => <Text>AudioPlayer</Text> +}) + +describe('VocabularyList', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + const onItemPress = jest.fn() + const documents = new DocumentBuilder(2).build() + + it('should display vocabulary list', () => { + mockUseLoadAsyncWithData(documents) + + const { getByText, getAllByText, getAllByTestId } = render( + <VocabularyList documents={documents} onItemPress={onItemPress} /> + ) + + expect(getByText(labels.exercises.vocabularyList.title)).toBeTruthy() + expect(getByText(`2 ${labels.general.words}`)).toBeTruthy() + expect(getByText('Der')).toBeTruthy() + expect(getByText('Spachtel')).toBeTruthy() + expect(getByText('Das')).toBeTruthy() + expect(getByText('Auto')).toBeTruthy() + expect(getAllByText('AudioPlayer')).toHaveLength(2) + expect(getAllByTestId('image')).toHaveLength(2) + }) + + it('should call onItemPress with correct index', () => { + const { getByText } = render(<VocabularyList documents={documents} onItemPress={onItemPress} />) + + expect(onItemPress).toHaveBeenCalledTimes(0) + + fireEvent.press(getByText('Auto')) + + expect(onItemPress).toHaveBeenCalledTimes(1) + expect(onItemPress).toHaveBeenCalledWith(1) + + fireEvent.press(getByText('Spachtel')) + + expect(onItemPress).toHaveBeenCalledTimes(2) + expect(onItemPress).toHaveBeenCalledWith(0) + }) +}) diff --git a/src/components/__tests__/VocabularyListItem.spec.tsx b/src/components/__tests__/VocabularyListItem.spec.tsx index 8ddf0cbe5..ed668dc66 100644 --- a/src/components/__tests__/VocabularyListItem.spec.tsx +++ b/src/components/__tests__/VocabularyListItem.spec.tsx @@ -1,3 +1,4 @@ +import { fireEvent } from '@testing-library/react-native' import React from 'react' import 'react-native' @@ -6,9 +7,11 @@ import { Document } from '../../constants/endpoints' import render from '../../testing/render' import VocabularyListItem from '../VocabularyListItem' -jest.mock('../../../../components/AudioPlayer', () => () => null) +jest.mock('../AudioPlayer', () => () => null) describe('VocabularyListItem', () => { + const onPress = jest.fn() + const document: Document = { article: ARTICLES[1], audio: '', @@ -19,17 +22,21 @@ describe('VocabularyListItem', () => { } it('should display image passed to it', () => { - const { getByTestId } = render(<VocabularyListItem document={document} />) + const { getByTestId } = render(<VocabularyListItem document={document} onPress={onPress} />) expect(getByTestId('image')).toHaveProp('source', { uri: document.document_image[0].image }) }) it('should display article passed to it', () => { - const { queryByText } = render(<VocabularyListItem document={document} />) + const { queryByText } = render(<VocabularyListItem document={document} onPress={onPress} />) expect(queryByText(document.article.value)).toBeTruthy() }) it('should display word passed to it', () => { - const { queryByText } = render(<VocabularyListItem document={document} />) - expect(queryByText(document.word)).toBeTruthy() + const { getByText } = render(<VocabularyListItem document={document} onPress={onPress} />) + expect(getByText(document.word)).toBeTruthy() + + expect(onPress).toHaveBeenCalledTimes(0) + fireEvent.press(getByText(document.word)) + expect(onPress).toHaveBeenCalledTimes(1) }) }) diff --git a/src/components/__tests__/VocabularyListModal.spec.tsx b/src/components/__tests__/VocabularyListModal.spec.tsx deleted file mode 100644 index eb36ff615..000000000 --- a/src/components/__tests__/VocabularyListModal.spec.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { fireEvent } from '@testing-library/react-native' -import React from 'react' - -import labels from '../../constants/labels.json' -import DocumentBuilder from '../../testing/DocumentBuilder' -import render from '../../testing/render' -import VocabularyListModal from '../VocabularyListModal' - -jest.mock('../../../../components/AudioPlayer', () => { - const Text = require('react-native').Text - return () => <Text>AudioPlayer</Text> -}) - -describe('VocabularyListModal', () => { - const documents = new DocumentBuilder(2).build() - - const setIsModalVisible = jest.fn() - const setSelectedDocumentIndex = jest.fn() - - it('should update current document', () => { - const { getByText } = render( - <VocabularyListModal - documents={documents} - isModalVisible - setIsModalVisible={setIsModalVisible} - selectedDocumentIndex={0} - setSelectedDocumentIndex={setSelectedDocumentIndex} - /> - ) - const button = getByText(labels.exercises.next) - expect(button).toBeDefined() - fireEvent.press(button) - expect(setSelectedDocumentIndex).toHaveBeenCalledTimes(1) - }) - - it('should close modal for last word', () => { - const { getByText } = render( - <VocabularyListModal - documents={documents} - isModalVisible - setIsModalVisible={setIsModalVisible} - selectedDocumentIndex={1} - setSelectedDocumentIndex={setSelectedDocumentIndex} - /> - ) - const button = getByText(labels.general.header.cancelExercise) - expect(button).toBeDefined() - fireEvent.press(button) - expect(setIsModalVisible).toHaveBeenCalledTimes(1) - }) -}) diff --git a/src/navigation/NavigationTypes.ts b/src/navigation/NavigationTypes.ts index 874bcde4a..0d71cf206 100644 --- a/src/navigation/NavigationTypes.ts +++ b/src/navigation/NavigationTypes.ts @@ -16,6 +16,14 @@ interface ExerciseParams { closeExerciseAction: CommonNavigationAction } +interface DetailExerciseParams { + disciplineId: number + disciplineTitle: string + documents: Document[] + documentIndex: number + closeExerciseAction: CommonNavigationAction +} + export interface ExercisesParams extends Omit<ExerciseParams, 'documents' | 'closeExerciseAction'> { discipline: Discipline documents: Document[] | null @@ -47,6 +55,7 @@ export type RoutesParams = { discipline: Discipline initialSelection: boolean } + VocabularyDetail: DetailExerciseParams Exercises: ExercisesParams VocabularyList: ExerciseParams WordChoiceExercise: ExerciseParams diff --git a/src/navigation/Navigator.tsx b/src/navigation/Navigator.tsx index 6573d24fe..fbdb9515e 100644 --- a/src/navigation/Navigator.tsx +++ b/src/navigation/Navigator.tsx @@ -7,6 +7,7 @@ import labels from '../constants/labels.json' import { useTabletHeaderHeight } from '../hooks/useTabletHeaderHeight' import ResultDetailScreen from '../routes/ResultDetailScreen' import ResultScreen from '../routes/ResultScreen' +import VocabularyDetailScreen from '../routes/VocabularyDetailScreen' import VocabularyListScreen from '../routes/VocabularyListScreen' import ArticleChoiceExerciseScreen from '../routes/choice-exercises/ArticleChoiceExerciseScreen' import WordChoiceExerciseScreen from '../routes/choice-exercises/WordChoiceExerciseScreen' @@ -33,6 +34,11 @@ const HomeStackNavigator = (): JSX.Element | null => { component={VocabularyListScreen} options={({ navigation }) => options(overviewExercises, navigation, false)} /> + <Stack.Screen + name='VocabularyDetail' + component={VocabularyDetailScreen} + options={({ navigation }) => options('', navigation, true)} + /> <Stack.Screen name='WordChoiceExercise' component={WordChoiceExerciseScreen} diff --git a/src/routes/ResultDetailScreen.tsx b/src/routes/ResultDetailScreen.tsx index 6677479fb..e671c2349 100644 --- a/src/routes/ResultDetailScreen.tsx +++ b/src/routes/ResultDetailScreen.tsx @@ -73,7 +73,12 @@ const ResultDetailScreen = ({ route, navigation }: ResultScreenProps): JSX.Eleme /> ) - const Item = ({ item }: { item: DocumentResult }): JSX.Element => <VocabularyListItem document={item.document} /> + const Item = ({ item, index }: { item: DocumentResult; index: number }): JSX.Element => ( + <VocabularyListItem + document={item.document} + onPress={() => navigation.navigate('VocabularyDetail', { ...route.params, documentIndex: index })} + /> + ) const repeatIncorrectEntries = (): void => navigation.navigate('WriteExercise', { diff --git a/src/routes/VocabularyDetailScreen.tsx b/src/routes/VocabularyDetailScreen.tsx new file mode 100644 index 000000000..00eb6fa47 --- /dev/null +++ b/src/routes/VocabularyDetailScreen.tsx @@ -0,0 +1,62 @@ +import { RouteProp } from '@react-navigation/native' +import { StackNavigationProp } from '@react-navigation/stack' +import React, { ReactElement } from 'react' +import styled from 'styled-components/native' + +import { ArrowRightIcon } from '../../assets/images' +import AudioPlayer from '../components/AudioPlayer' +import Button from '../components/Button' +import ImageCarousel from '../components/ImageCarousel' +import WordItem from '../components/WordItem' +import { BUTTONS_THEME } from '../constants/data' +import labels from '../constants/labels.json' +import { RoutesParams } from '../navigation/NavigationTypes' + +const ItemContainer = styled.View` + margin: ${props => props.theme.spacings.xl} 0; + height: 10%; + width: 85%; + align-self: center; +` + +const ButtonContainer = styled.View` + display: flex; + align-self: center; +` + +interface VocabularyDetailScreenProps { + route: RouteProp<RoutesParams, 'VocabularyDetail'> + navigation: StackNavigationProp<RoutesParams, 'VocabularyDetail'> +} + +const VocabularyDetailScreen = ({ route, navigation }: VocabularyDetailScreenProps): ReactElement => { + const { documents, documentIndex } = route.params + const document = documents[documentIndex] + const { word, article, document_image: image } = document + const hasNextDocument = documentIndex + 1 < documents.length + + const goToNextWord = () => + navigation.navigate('VocabularyDetail', { ...route.params, documentIndex: documentIndex + 1 }) + + return ( + <> + <ImageCarousel images={image} /> + <AudioPlayer document={document} disabled={false} /> + <ItemContainer> + <WordItem answer={{ word, article }} /> + </ItemContainer> + <ButtonContainer> + {hasNextDocument && ( + <Button + label={labels.exercises.next} + iconRight={ArrowRightIcon} + onPress={goToNextWord} + buttonTheme={BUTTONS_THEME.contained} + /> + )} + </ButtonContainer> + </> + ) +} + +export default VocabularyDetailScreen diff --git a/src/routes/VocabularyListScreen.tsx b/src/routes/VocabularyListScreen.tsx index 2d05f94cf..80e9fce0d 100644 --- a/src/routes/VocabularyListScreen.tsx +++ b/src/routes/VocabularyListScreen.tsx @@ -10,8 +10,10 @@ interface VocabularyListScreenProps { navigation: StackNavigationProp<RoutesParams, 'VocabularyList'> } -const VocabularyListScreen = ({ route }: VocabularyListScreenProps): JSX.Element => ( - <VocabularyList documents={route.params.documents} /> -) +const VocabularyListScreen = ({ route, navigation }: VocabularyListScreenProps): JSX.Element => { + const onItemPress = (index: number) => + navigation.navigate('VocabularyDetail', { ...route.params, documentIndex: index }) + return <VocabularyList documents={route.params.documents} onItemPress={onItemPress} /> +} export default VocabularyListScreen diff --git a/src/routes/__tests__/VocabularyListScreen.spec.tsx b/src/routes/__tests__/VocabularyListScreen.spec.tsx deleted file mode 100644 index 03e6d9844..000000000 --- a/src/routes/__tests__/VocabularyListScreen.spec.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { fireEvent } from '@testing-library/react-native' -import React, { ComponentProps } from 'react' - -import VocabularyList from '../../components/VocabularyList' -import VocabularyListModal from '../../components/VocabularyListModal' -import labels from '../../constants/labels.json' -import DocumentBuilder from '../../testing/DocumentBuilder' -import { mockUseLoadAsyncWithData } from '../../testing/mockUseLoadFromEndpoint' -import render from '../../testing/render' - -jest.mock('../../../components/AudioPlayer', () => { - const Text = require('react-native').Text - return () => <Text>AudioPlayer</Text> -}) - -// fix that the modal appears in the test tree even though it is not visible -jest.mock('react-native/Libraries/Modal/Modal', () => { - const Modal = jest.requireActual('react-native/Libraries/Modal/Modal') - // @ts-expect-error test - return props => <Modal {...props} /> -}) - -// the modal always lies on top of the route meaning you'll also find the texts below -// therefore prefix the word with modal_ -// requireing the actual modal fixes that the modal appears in the test tree -// even though it is not visible - -jest.mock('../components/VocabularyListModal', () => { - const Modal = jest.requireActual('react-native/Libraries/Modal/Modal') - const Text = require('react-native').Text - return ({ isModalVisible, documents, selectedDocumentIndex }: ComponentProps<typeof VocabularyListModal>) => ( - <Modal visible={isModalVisible}> - <Text>{`modal_${documents[selectedDocumentIndex].word}`}</Text> - </Modal> - ) -}) - -describe('VocabularyList', () => { - const documents = new DocumentBuilder(2).build() - - it('should display vocabulary list', () => { - mockUseLoadAsyncWithData(documents) - - const { getByText, getAllByText, getAllByTestId } = render(<VocabularyList documents={documents} />) - - expect(getByText(labels.exercises.vocabularyList.title)).toBeTruthy() - expect(getByText(`2 ${labels.general.words}`)).toBeTruthy() - expect(getByText('Der')).toBeTruthy() - expect(getByText('Spachtel')).toBeTruthy() - expect(getByText('Das')).toBeTruthy() - expect(getByText('Auto')).toBeTruthy() - expect(getAllByText('AudioPlayer')).toHaveLength(2) - expect(getAllByTestId('image')).toHaveLength(2) - }) - - it('should open vocabulary modal when pressing vocabulary', () => { - const { getByText } = render(<VocabularyList documents={documents} />) - - const vocabulary = getByText('Auto') - fireEvent.press(vocabulary) - - // see mock above - expect(getByText('modal_Auto')).toBeTruthy() - }) -}) diff --git a/src/routes/home/HomeScreen.tsx b/src/routes/home/HomeScreen.tsx index 57f31947e..ae092e8eb 100644 --- a/src/routes/home/HomeScreen.tsx +++ b/src/routes/home/HomeScreen.tsx @@ -7,6 +7,7 @@ import styled from 'styled-components/native' import HeaderWithMenu from '../../components/HeaderWithMenu' import { ContentSecondary } from '../../components/text/Content' import { Heading } from '../../components/text/Heading' +import { Discipline } from '../../constants/endpoints' import labels from '../../constants/labels.json' import useReadCustomDisciplines from '../../hooks/useReadCustomDisciplines' import useReadSelectedProfessions from '../../hooks/useReadSelectedProfessions' @@ -48,16 +49,25 @@ const HomeScreen = ({ navigation }: HomeScreenProps): JSX.Element => { navigation.navigate('AddCustomDiscipline') } + const navigateToDisciplineSelection = (discipline: Discipline): void => { + navigation.navigate('DisciplineSelection', { discipline }) + } + const customDisciplineItems = customDisciplines?.map(customDiscipline => ( <DisciplineCard key={customDiscipline} identifier={{ apiKey: customDiscipline }} refresh={refreshCustomDisciplines} + onPress={navigateToDisciplineSelection} /> )) const selectedProfessionItems = selectedProfessions?.map(profession => ( - <DisciplineCard key={profession} identifier={{ disciplineId: profession }} /> + <DisciplineCard + key={profession} + identifier={{ disciplineId: profession }} + onPress={navigateToDisciplineSelection} + /> )) return ( diff --git a/src/routes/home/components/DisciplineCard.tsx b/src/routes/home/components/DisciplineCard.tsx index 78203f7f0..7c9ebbdde 100644 --- a/src/routes/home/components/DisciplineCard.tsx +++ b/src/routes/home/components/DisciplineCard.tsx @@ -7,7 +7,7 @@ import Loading from '../../../components/Loading' import { ContentSecondary, ContentSecondaryLight } from '../../../components/text/Content' import { Subheading } from '../../../components/text/Subheading' import { BUTTONS_THEME } from '../../../constants/data' -import { ForbiddenError } from '../../../constants/endpoints' +import { Discipline, ForbiddenError } from '../../../constants/endpoints' import labels from '../../../constants/labels.json' import { isTypeLoadProtected } from '../../../hooks/helpers' import { RequestParams, useLoadDiscipline } from '../../../hooks/useLoadDiscipline' @@ -40,9 +40,10 @@ export const ButtonContainer = styled.View` interface PropsType { identifier: RequestParams refresh?: () => void + onPress: (discipline: Discipline) => void } -const DisciplineCard = ({ identifier, refresh: refreshHome }: PropsType): JSX.Element => { +const DisciplineCard = ({ identifier, refresh: refreshHome, onPress }: PropsType): JSX.Element => { const { data: discipline, loading, error, refresh } = useLoadDiscipline(identifier) if (loading) { @@ -79,7 +80,7 @@ const DisciplineCard = ({ identifier, refresh: refreshHome }: PropsType): JSX.El } return ( - <Card heading={discipline.title} icon={discipline.icon}> + <Card heading={discipline.title} icon={discipline.icon} onPress={() => onPress(discipline)}> {isTypeLoadProtected(identifier) ? ( <CustomDisciplineDetails discipline={discipline} /> ) : ( diff --git a/src/routes/home/components/__tests__/DisciplineCard.spec.tsx b/src/routes/home/components/__tests__/DisciplineCard.spec.tsx index 83eb766fb..49d1765fe 100644 --- a/src/routes/home/components/__tests__/DisciplineCard.spec.tsx +++ b/src/routes/home/components/__tests__/DisciplineCard.spec.tsx @@ -1,5 +1,5 @@ import { NavigationContainer } from '@react-navigation/native' -import { RenderAPI } from '@testing-library/react-native' +import { fireEvent, RenderAPI } from '@testing-library/react-native' import React from 'react' import { ForbiddenError, NetworkError } from '../../../../constants/endpoints' @@ -16,10 +16,16 @@ import DisciplineCard from '../DisciplineCard' jest.useFakeTimers() describe('DisciplineCard', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + const onPress = jest.fn() + const renderDisciplineCard = (): RenderAPI => render( <NavigationContainer> - <DisciplineCard identifier={{ disciplineId: 1 }} /> + <DisciplineCard identifier={{ disciplineId: 1 }} onPress={onPress} /> </NavigationContainer> ) @@ -29,6 +35,11 @@ describe('DisciplineCard', () => { expect(getByText(mockDisciplines()[0].title)).toBeDefined() expect(getByTestId('progress-circle')).toBeDefined() await expect(findByText(labels.home.continue)).toBeDefined() + + fireEvent.press(getByText(mockDisciplines()[0].title)) + + expect(onPress).toHaveBeenCalledTimes(1) + expect(onPress).toHaveBeenCalledWith(mockDisciplines()[0]) }) it('should display loading', () => { @@ -47,7 +58,7 @@ describe('DisciplineCard', () => { mockUseLoadAsyncWithError(ForbiddenError) const { getByText } = render( <NavigationContainer> - <DisciplineCard identifier={{ apiKey: 'abc' }} /> + <DisciplineCard identifier={{ apiKey: 'abc' }} onPress={onPress} /> </NavigationContainer> ) expect(getByText(`${labels.home.errorLoadCustomDiscipline} abc`)).toBeDefined()