Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(PC-31778) feat(Bookings): post reaction on tab change or on change screen #6909

Merged
merged 7 commits into from
Sep 23, 2024
19 changes: 15 additions & 4 deletions src/api/gen/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2710,20 +2710,31 @@ export interface PostFeedbackBody {
}
/**
* @export
* @interface PostReactionRequest
* @interface PostOneReactionRequest
*/
export interface PostReactionRequest {
export interface PostOneReactionRequest {
/**
* @type {number}
* @memberof PostReactionRequest
* @memberof PostOneReactionRequest
*/
offerId: number
/**
* @type {ReactionTypeEnum}
* @memberof PostReactionRequest
* @memberof PostOneReactionRequest
*/
reactionType: ReactionTypeEnum
}
/**
* @export
* @interface PostReactionRequest
*/
export interface PostReactionRequest {
/**
* @type {Array<PostOneReactionRequest>}
* @memberof PostReactionRequest
*/
reactions: Array<PostOneReactionRequest>
}
/**
* @export
* @interface ProfileUpdateRequest
Expand Down
5 changes: 2 additions & 3 deletions src/features/bookings/components/EndedBookingItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { ComponentProps, FunctionComponent, useCallback, useMemo, useStat
import { View } from 'react-native'
import styled from 'styled-components/native'

import { BookingCancellationReasons, PostReactionRequest, ReactionTypeEnum } from 'api/gen'
import { BookingCancellationReasons, PostOneReactionRequest, ReactionTypeEnum } from 'api/gen'
import { BookingItemTitle } from 'features/bookings/components/BookingItemTitle'
import { isEligibleBookingsForArchive } from 'features/bookings/helpers/expirationDateUtils'
import { BookingItemProps } from 'features/bookings/types'
Expand Down Expand Up @@ -50,7 +50,6 @@ export const EndedBookingItem = ({ booking, onSaveReaction }: BookingItemProps)
const { cancellationDate, cancellationReason, dateUsed, stock } = booking
const subcategoriesMapping = useSubcategoriesMapping()
const subCategory = subcategoriesMapping[stock.offer.subcategoryId]

const prePopulateOffer = usePrePopulateOffer()
const netInfo = useNetInfoContext()
const { showErrorSnackBar } = useSnackBarContext()
Expand Down Expand Up @@ -150,7 +149,7 @@ export const EndedBookingItem = ({ booking, onSaveReaction }: BookingItemProps)
utmMedium: 'ended_booking',
})

const handleSaveReaction = async ({ offerId, reactionType }: PostReactionRequest) => {
const handleSaveReaction = async ({ offerId, reactionType }: PostOneReactionRequest) => {
await onSaveReaction?.({ offerId, reactionType })
setUserReaction(reactionType)
hideReactionModal()
Expand Down
3 changes: 3 additions & 0 deletions src/features/bookings/fixtures/bookingsSnap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { ReadonlyDeep } from 'type-fest'
import {
BookingCancellationReasons,
BookingsResponse,
ReactionTypeEnum,
SubcategoryIdEnum,
WithdrawalTypeEnum,
} from 'api/gen'
Expand Down Expand Up @@ -66,6 +67,7 @@ export const bookingsSnap = toMutable({
totalAmount: 1900,
token: '352UW5',
quantity: 10,
userReaction: null,
stock: {
id: 150230,
price: 400,
Expand Down Expand Up @@ -106,6 +108,7 @@ export const bookingsSnap = toMutable({
totalAmount: 1900,
token: '352UW5',
quantity: 10,
userReaction: ReactionTypeEnum.LIKE,
stock: {
id: 150230,
price: 400,
Expand Down
39 changes: 35 additions & 4 deletions src/features/bookings/pages/Bookings/Bookings.native.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react'
import { QueryObserverResult } from 'react-query'

import { navigate } from '__mocks__/@react-navigation/native'
import { BookingsResponse, SubcategoriesResponseModelv2 } from 'api/gen'
import { BookingsResponse, ReactionTypeEnum, SubcategoriesResponseModelv2 } from 'api/gen'
import * as bookingsAPI from 'features/bookings/api/useBookings'
import { bookingsSnap, emptyBookingsSnap } from 'features/bookings/fixtures/bookingsSnap'
import * as useFeatureFlagAPI from 'libs/firebase/firestore/featureFlags/useFeatureFlag'
Expand Down Expand Up @@ -49,6 +49,11 @@ jest.mock('react-native/Libraries/Animated/createAnimatedComponent', () => {
}
})

const mockMutate = jest.fn()
jest.mock('features/reactions/api/useReactionMutation', () => ({
useReactionMutation: () => ({ mutate: mockMutate }),
}))

describe('Bookings', () => {
beforeEach(() => {
mockServer.getApi<SubcategoriesResponseModelv2>('/v1/subcategories/v2', subcategoriesDataTest)
Expand All @@ -66,8 +71,8 @@ describe('Bookings', () => {
renderBookings()
await act(async () => {})

//Due to multiple renders useBookings is called twice
expect(useBookingsSpy).toHaveBeenCalledTimes(2)
//Due to multiple renders useBookings is called three times
expect(useBookingsSpy).toHaveBeenCalledTimes(3)
})

it('should display the right number of ongoing bookings', async () => {
Expand All @@ -82,10 +87,11 @@ describe('Bookings', () => {
data: emptyBookingsSnap,
isFetching: false,
} as unknown as QueryObserverResult<BookingsResponse, unknown>
// Due to multiple renders we need to mock useBookings twice
// Due to multiple renders we need to mock useBookings three times
useBookingsSpy
.mockReturnValueOnce(useBookingsResultMock)
.mockReturnValueOnce(useBookingsResultMock)
.mockReturnValueOnce(useBookingsResultMock)
renderBookings()

await act(async () => {})
Expand All @@ -112,6 +118,9 @@ describe('Bookings', () => {

describe('when feature flag is activated', () => {
beforeEach(() => {
// Due to multiple renders we need to mock useBookings three times
useFeatureFlagSpy.mockReturnValueOnce(true)
useFeatureFlagSpy.mockReturnValueOnce(true)
useFeatureFlagSpy.mockReturnValueOnce(true)
})

Expand All @@ -135,6 +144,28 @@ describe('Bookings', () => {

expect(await screen.findAllByText('Avez-vous déjà vu\u00a0?')).toHaveLength(2)
})

it('should call updateReactions when switching from COMPLETED tab', async () => {
renderBookings()

fireEvent.press(await screen.findByText('Terminées'))

fireEvent.press(await screen.findByText('En cours'))

expect(mockMutate).toHaveBeenCalledTimes(1)
})

it('should update reactions for ended bookings without user reaction', async () => {
renderBookings()

fireEvent.press(await screen.findByText('Terminées'))

fireEvent.press(await screen.findByText('En cours'))

expect(mockMutate).toHaveBeenCalledWith({
reactions: [{ offerId: 147874, reactionType: ReactionTypeEnum.NO_REACTION }],
})
})
})
})

Expand Down
45 changes: 43 additions & 2 deletions src/features/bookings/pages/Bookings/Bookings.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import React from 'react'
import { useFocusEffect } from '@react-navigation/native'
import React, { useCallback, useState } from 'react'
import styled from 'styled-components/native'

import { ReactionTypeEnum } from 'api/gen'
import { useBookings } from 'features/bookings/api'
import { OnGoingBookingsList } from 'features/bookings/components/OnGoingBookingsList'
import { EndedBookings } from 'features/bookings/pages/EndedBookings/EndedBookings'
import { useReactionMutation } from 'features/reactions/api/useReactionMutation'
import { TabLayout } from 'features/venue/components/TabLayout/TabLayout'
import { useFeatureFlag } from 'libs/firebase/firestore/featureFlags/useFeatureFlag'
import { RemoteStoreFeatureFlags } from 'libs/firebase/firestore/types'
Expand All @@ -16,10 +20,44 @@ enum Tab {

export function Bookings() {
const enableBookingImprove = useFeatureFlag(RemoteStoreFeatureFlags.WIP_BOOKING_IMPROVE)
const [activeTab, setActiveTab] = useState<Tab>(Tab.CURRENT)
const [previousTab, setPreviousTab] = useState(activeTab)
const { data: bookings } = useBookings()
const { mutate: addReaction } = useReactionMutation()

const updateReactions = useCallback(() => {
const bookingsToUpdate =
bookings?.ended_bookings
.filter((ended_booking) => ended_booking.userReaction === null)
.map((booking) => booking.stock.offer.id) ?? []

const bookingsTest = bookingsToUpdate.map((bookingId) => ({
offerId: bookingId,
reactionType: ReactionTypeEnum.NO_REACTION,
}))
if (bookingsTest.length > 0) {
addReaction({ reactions: bookingsTest })
}
}, [addReaction, bookings?.ended_bookings])

useFocusEffect(
useCallback(() => {
return () => {
if (previousTab === Tab.COMPLETED) {
updateReactions()
}
setPreviousTab(activeTab)
}
}, [activeTab, previousTab, updateReactions])
)

const tabPanels = {
[Tab.CURRENT]: <OnGoingBookingsList enableBookingImprove={enableBookingImprove} />,
[Tab.COMPLETED]: <EndedBookings enableBookingImprove={enableBookingImprove} />,
[Tab.COMPLETED]: (
<EndedBookings enableBookingImprove={enableBookingImprove} bookings={bookings} />
),
}

return (
<React.Fragment>
{enableBookingImprove ? (
Expand All @@ -29,6 +67,9 @@ export function Bookings() {
tabPanels={tabPanels}
defaultTab={Tab.CURRENT}
tabs={[{ key: Tab.CURRENT }, { key: Tab.COMPLETED }]}
onTabChange={(key) => {
setActiveTab(key)
}}
/>
</ContainerTab>
) : (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import React, { Fragment, FunctionComponent } from 'react'
import { QueryObserverResult } from 'react-query'

import { BookingsResponse } from 'api/gen'
import * as bookingsAPI from 'features/bookings/api/useBookings'
import { bookingsSnap } from 'features/bookings/fixtures/bookingsSnap'
import * as useGoBack from 'features/navigation/useGoBack'
import * as useFeatureFlagAPI from 'libs/firebase/firestore/featureFlags/useFeatureFlag'
Expand Down Expand Up @@ -62,13 +60,6 @@ describe('EndedBookings', () => {
expect(screen).toMatchSnapshot()
})

it('should always execute the query (in cache or in network)', () => {
const useBookings = jest.spyOn(bookingsAPI, 'useBookings')
renderEndedBookings(bookingsSnap)

expect(useBookings).toHaveBeenCalledTimes(1)
})

it('should display the right number of ended bookings', () => {
renderEndedBookings(bookingsSnap)

Expand Down Expand Up @@ -107,11 +98,9 @@ const renderEndedBookings = (
bookings: BookingsResponse,
Wrapper: FunctionComponent<{ children: JSX.Element }> = Fragment
) => {
jest
.spyOn(bookingsAPI, 'useBookings')
.mockReturnValue({ data: bookings } as QueryObserverResult<BookingsResponse, unknown>)

return render(
<Wrapper>{reactQueryProviderHOC(<EndedBookings enableBookingImprove={false} />)}</Wrapper>
<Wrapper>
{reactQueryProviderHOC(<EndedBookings enableBookingImprove={false} bookings={bookings} />)}
</Wrapper>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe('<EndedBookings />', () => {
await measurePerformance(
reactQueryProviderHOC(
<AuthWrapper>
<EndedBookings enableBookingImprove={false} />
<EndedBookings enableBookingImprove={false} bookings={bookingsSnap} />
</AuthWrapper>
),
{
Expand Down
14 changes: 8 additions & 6 deletions src/features/bookings/pages/EndedBookings/EndedBookings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import React, { FunctionComponent, useCallback } from 'react'
import { FlatList, ListRenderItem } from 'react-native'
import styled from 'styled-components/native'

import { PostReactionRequest } from 'api/gen'
import { useBookings } from 'features/bookings/api'
import { BookingsResponse, PostOneReactionRequest, PostReactionRequest } from 'api/gen'
import { EndedBookingItem } from 'features/bookings/components/EndedBookingItem'
import { NoBookingsView } from 'features/bookings/components/NoBookingsView'
import { Booking } from 'features/bookings/types'
Expand All @@ -25,10 +24,10 @@ const keyExtractor: (item: Booking) => string = (item) => item.id.toString()

type Props = {
enableBookingImprove: boolean
bookings: BookingsResponse | undefined
}

export const EndedBookings: FunctionComponent<Props> = ({ enableBookingImprove }) => {
const { data: bookings } = useBookings()
export const EndedBookings: FunctionComponent<Props> = ({ enableBookingImprove, bookings }) => {
const { goBack } = useGoBack(...getTabNavConfig('Bookings'))
const headerHeight = useGetHeaderHeight()
const { mutate: addReaction } = useReactionMutation()
Expand All @@ -40,8 +39,11 @@ export const EndedBookings: FunctionComponent<Props> = ({ enableBookingImprove }
})

const handleSaveReaction = useCallback(
({ offerId, reactionType }: PostReactionRequest) => {
addReaction({ offerId, reactionType })
({ offerId, reactionType }: PostOneReactionRequest) => {
const reactionRequest: PostReactionRequest = {
reactions: [{ offerId, reactionType }],
}
addReaction(reactionRequest)
return Promise.resolve(true)
},
[addReaction]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,7 @@ const renderEndedBookings = (bookings: BookingsResponse) => {
.spyOn(bookingsAPI, 'useBookings')
.mockReturnValue({ data: bookings } as QueryObserverResult<BookingsResponse, unknown>)

return render(reactQueryProviderHOC(<EndedBookings enableBookingImprove={false} />))
return render(
reactQueryProviderHOC(<EndedBookings enableBookingImprove={false} bookings={bookingsSnap} />)
)
}
4 changes: 2 additions & 2 deletions src/features/bookings/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BookingReponse, PostReactionRequest } from 'api/gen'
import { BookingReponse, PostOneReactionRequest } from 'api/gen'

export type BookingProperties = {
isDuo?: boolean
Expand All @@ -14,5 +14,5 @@ export type Booking = BookingReponse
export interface BookingItemProps {
booking: Booking
eligibleBookingsForArchive?: Booking[]
onSaveReaction?: (reactionParams: PostReactionRequest) => Promise<boolean>
onSaveReaction?: (reactionParams: PostOneReactionRequest) => Promise<boolean>
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useCallback } from 'react'

import { PostReactionRequest, ReactionTypeEnum } from 'api/gen'
import { PostOneReactionRequest, PostReactionRequest, ReactionTypeEnum } from 'api/gen'
import { useBookings } from 'features/bookings/api'
import { TWENTY_FOUR_HOURS } from 'features/home/constants'
import { useReactionMutation } from 'features/reactions/api/useReactionMutation'
Expand Down Expand Up @@ -30,8 +30,11 @@ export const IncomingReactionModalContainer = () => {
const firstBooking = bookingsWithoutReaction[0]

const handleSaveReaction = useCallback(
({ offerId, reactionType }: PostReactionRequest) => {
addReaction({ offerId, reactionType })
({ offerId, reactionType }: PostOneReactionRequest) => {
const reactionRequest: PostReactionRequest = {
reactions: [{ offerId, reactionType }],
}
addReaction(reactionRequest)
return Promise.resolve(true)
},
[addReaction]
Expand All @@ -41,8 +44,12 @@ export const IncomingReactionModalContainer = () => {
if (!firstBooking) return

addReaction({
offerId: firstBooking.stock.offer.id,
reactionType: ReactionTypeEnum.NO_REACTION,
reactions: [
{
offerId: firstBooking.stock.offer.id,
reactionType: ReactionTypeEnum.NO_REACTION,
},
],
})

hideReactionModal()
Expand Down
Loading
Loading