From ca6f91e3524971eb37139ba37737b44b61b4398a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?In=C3=A8s=20Mouandjo=20Lob=C3=A9?=
<158567429+imouandjolobe-pass@users.noreply.github.com>
Date: Fri, 17 Jan 2025 12:45:00 +0100
Subject: [PATCH 01/12] fix: re align arrow
---
src/ui/components/tiles/HorizontalTile.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/ui/components/tiles/HorizontalTile.tsx b/src/ui/components/tiles/HorizontalTile.tsx
index 0994efcf27b..d10d786e4ae 100644
--- a/src/ui/components/tiles/HorizontalTile.tsx
+++ b/src/ui/components/tiles/HorizontalTile.tsx
@@ -69,6 +69,7 @@ const Column = Flex
const Row = styled(Flex).attrs({
flexDirection: 'row',
+ alignItems: 'center',
})``
const Container = styled(Row).attrs({
From 8b6460726f2f90194520080c4d718d90dbd46e49 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?In=C3=A8s=20Mouandjo=20Lob=C3=A9?=
<158567429+imouandjolobe-pass@users.noreply.github.com>
Date: Tue, 21 Jan 2025 11:34:28 +0100
Subject: [PATCH 02/12] refactor: extract MovieScreenig in its own file and
rename
---
.../components/VenueOffers/VenueMovies.tsx | 50 ++++++++++++++++
.../components/VenueOffers/VenueOffers.tsx | 57 ++-----------------
2 files changed, 56 insertions(+), 51 deletions(-)
create mode 100644 src/features/venue/components/VenueOffers/VenueMovies.tsx
diff --git a/src/features/venue/components/VenueOffers/VenueMovies.tsx b/src/features/venue/components/VenueOffers/VenueMovies.tsx
new file mode 100644
index 00000000000..424a0ba3989
--- /dev/null
+++ b/src/features/venue/components/VenueOffers/VenueMovies.tsx
@@ -0,0 +1,50 @@
+import React, { useEffect } from 'react'
+import { InView } from 'react-native-intersection-observer'
+import { useTheme } from 'styled-components'
+import styled from 'styled-components/native'
+
+import { MoviesScreeningCalendar } from 'features/offer/components/MoviesScreeningCalendar/MoviesScreeningCalendar'
+import { useOfferCTA } from 'features/offer/components/OfferContent/OfferCTAProvider'
+import type { VenueOffers } from 'features/venue/types'
+import { Anchor } from 'ui/components/anchor/Anchor'
+import { useScrollToAnchor } from 'ui/components/anchor/AnchorContext'
+import { Spacer, TypoDS, getSpacing } from 'ui/theme'
+import { getHeadingAttrs } from 'ui/theme/typographyAttrs/getHeadingAttrs'
+
+const cinemaCTAButtonName = 'Accéder aux séances'
+
+export const VenueMovies: React.FC<{ venueOffers: VenueOffers }> = ({ venueOffers }) => {
+ const { isDesktopViewport } = useTheme()
+ const { setButton, showButton } = useOfferCTA()
+ const scrollToAnchor = useScrollToAnchor()
+
+ useEffect(() => {
+ setButton(cinemaCTAButtonName, () => {
+ scrollToAnchor('venue-cine-availabilities')
+ })
+
+ return () => {
+ setButton('', () => null)
+ }
+ }, [scrollToAnchor, setButton])
+
+ return (
+
+
+
+ {
+ showButton(!inView)
+ }}>
+ {'Les films à l’affiche'}
+
+
+
+
+
+ )
+}
+
+const MoviesTitle = styled(TypoDS.Title3).attrs(getHeadingAttrs(2))({
+ marginLeft: getSpacing(6),
+})
diff --git a/src/features/venue/components/VenueOffers/VenueOffers.tsx b/src/features/venue/components/VenueOffers/VenueOffers.tsx
index cfb948cb953..24a7a638e61 100644
--- a/src/features/venue/components/VenueOffers/VenueOffers.tsx
+++ b/src/features/venue/components/VenueOffers/VenueOffers.tsx
@@ -1,34 +1,25 @@
-import React, { useEffect } from 'react'
-import { InView } from 'react-native-intersection-observer'
-import { useTheme } from 'styled-components'
-import styled from 'styled-components/native'
+import React from 'react'
import { SubcategoryIdEnum, VenueResponse } from 'api/gen'
import { useGTLPlaylists } from 'features/gtlPlaylist/hooks/useGTLPlaylists'
import { GtlPlaylistData } from 'features/gtlPlaylist/types'
-import { MoviesScreeningCalendar } from 'features/offer/components/MoviesScreeningCalendar/MoviesScreeningCalendar'
-import { useOfferCTA } from 'features/offer/components/OfferContent/OfferCTAProvider'
import { useVenueOffers } from 'features/venue/api/useVenueOffers'
import { NoOfferPlaceholder } from 'features/venue/components/Placeholders/NoOfferPlaceholder'
+import { VenueMovies } from 'features/venue/components/VenueOffers/VenueMovies'
import { VenueOffersList } from 'features/venue/components/VenueOffers/VenueOffersList'
-import type { VenueOffersArtists, VenueOffers as VenueOffersType } from 'features/venue/types'
+import type { VenueOffersArtists, VenueOffers } from 'features/venue/types'
import { useFeatureFlag } from 'libs/firebase/firestore/featureFlags/useFeatureFlag'
import { RemoteStoreFeatureFlags } from 'libs/firebase/firestore/types'
-import { Anchor } from 'ui/components/anchor/Anchor'
-import { useScrollToAnchor } from 'ui/components/anchor/AnchorContext'
import { OfferPlaylistSkeleton, TileSize } from 'ui/components/placeholders/OfferPlaylistSkeleton'
-import { Spacer, TypoDS, getSpacing } from 'ui/theme'
-import { getHeadingAttrs } from 'ui/theme/typographyAttrs/getHeadingAttrs'
+import { Spacer } from 'ui/theme'
export interface VenueOffersProps {
venue: VenueResponse
venueArtists?: VenueOffersArtists
- venueOffers?: VenueOffersType
+ venueOffers?: VenueOffers
playlists?: GtlPlaylistData[]
}
-const cinemaCTAButtonName = 'Accéder aux séances'
-
const LoadingState: React.FC = () => (
@@ -36,38 +27,6 @@ const LoadingState: React.FC = () => (
)
-const MovieScreening: React.FC<{ venueOffers: VenueOffersType }> = ({ venueOffers }) => {
- const { isDesktopViewport } = useTheme()
- const { setButton, showButton } = useOfferCTA()
- const scrollToAnchor = useScrollToAnchor()
-
- useEffect(() => {
- setButton(cinemaCTAButtonName, () => {
- scrollToAnchor('venue-cine-availabilities')
- })
-
- return () => {
- setButton('', () => null)
- }
- }, [scrollToAnchor, setButton])
-
- return (
-
-
-
- {
- showButton(!inView)
- }}>
- {'Les films à l’affiche'}
-
-
-
-
-
- )
-}
-
export function VenueOffers({
venue,
venueArtists,
@@ -93,7 +52,7 @@ export function VenueOffers({
}
if (isOfferAMovieScreening && enableCine) {
- return
+ return
}
return (
@@ -105,7 +64,3 @@ export function VenueOffers({
/>
)
}
-
-const MoviesTitle = styled(TypoDS.Title3).attrs(getHeadingAttrs(2))({
- marginLeft: getSpacing(6),
-})
From ee25e7c16351a00d44d069c0379e207ea9aa4830 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?In=C3=A8s=20Mouandjo=20Lob=C3=A9?=
<158567429+imouandjolobe-pass@users.noreply.github.com>
Date: Tue, 21 Jan 2025 12:02:46 +0100
Subject: [PATCH 03/12] refactor: remove circular dependency
---
.../MovieOfferTile.tsx | 11 ++++-
.../moviesOffer.builder.ts | 42 +++++++++----------
.../MoviesScreeningCalendar/types.ts | 6 +++
3 files changed, 35 insertions(+), 24 deletions(-)
create mode 100644 src/features/offer/components/MoviesScreeningCalendar/types.ts
diff --git a/src/features/offer/components/MoviesScreeningCalendar/MovieOfferTile.tsx b/src/features/offer/components/MoviesScreeningCalendar/MovieOfferTile.tsx
index a4ed21add9e..85d1aca3bde 100644
--- a/src/features/offer/components/MoviesScreeningCalendar/MovieOfferTile.tsx
+++ b/src/features/offer/components/MoviesScreeningCalendar/MovieOfferTile.tsx
@@ -8,9 +8,10 @@ import {
getMovieScreenings,
} from 'features/offer/components/MovieScreeningCalendar/useMovieScreeningCalendar'
import { useSelectedDateScreening } from 'features/offer/components/MovieScreeningCalendar/useSelectedDateScreenings'
-import { MovieOffer } from 'features/offer/components/MoviesScreeningCalendar/getNextMoviesByDate'
import { useMovieCalendar } from 'features/offer/components/MoviesScreeningCalendar/MovieCalendarContext'
+import { isDateNotWithinNextNbDays } from 'features/offer/components/MoviesScreeningCalendar/moviesOffer.builder'
import { NextScreeningButton } from 'features/offer/components/MoviesScreeningCalendar/NextScreeningButton'
+import { MovieOffer } from 'features/offer/components/MoviesScreeningCalendar/types'
import { useOfferCTAButton } from 'features/offer/components/OfferCTAButton/useOfferCTAButton'
import { formatDuration } from 'features/offer/helpers/formatDuration/formatDuration'
import { VenueOffers } from 'features/venue/types'
@@ -57,10 +58,12 @@ export const MovieOfferTile: FC = ({
() => selectedDateScreenings(offer.venue.id, onPressOfferCTA, movieScreeningUserData),
[movieScreeningUserData, offer.venue.id, onPressOfferCTA, selectedDateScreenings]
)
+
const offerScreeningOnSelectedDates = useMemo(
() => venueOffers.hits.find((item) => Number(item.objectID) === offer.id),
[offer.id, venueOffers.hits]
)
+
return (
@@ -81,7 +84,11 @@ export const MovieOfferTile: FC = ({
goToDate(nextScreeningDate)}
+ onPress={
+ isDateNotWithinNextNbDays(new Date(), nextScreeningDate, 15)
+ ? () => onPressOfferCTA()
+ : () => goToDate(nextScreeningDate)
+ }
/>
) : (
diff --git a/src/features/offer/components/MoviesScreeningCalendar/moviesOffer.builder.ts b/src/features/offer/components/MoviesScreeningCalendar/moviesOffer.builder.ts
index 007399c5d8b..7d871e2a7cc 100644
--- a/src/features/offer/components/MoviesScreeningCalendar/moviesOffer.builder.ts
+++ b/src/features/offer/components/MoviesScreeningCalendar/moviesOffer.builder.ts
@@ -8,7 +8,7 @@ import {
} from 'date-fns'
import { OfferResponseV2, OfferStockResponse } from 'api/gen'
-import { MovieOffer } from 'features/offer/components/MoviesScreeningCalendar/getNextMoviesByDate'
+import { MovieOffer } from 'features/offer/components/MoviesScreeningCalendar/types'
import { GeoCoordinates } from 'libs/location'
import { computeDistanceInMeters } from 'libs/parsers/formatDistance'
@@ -22,7 +22,7 @@ export const moviesOfferBuilder = (offersWithStocks: OfferResponseV2[] = []) =>
withoutScreeningsOnDay: (selectedDate: Date) => {
movieOffers = movieOffers.filter(
({ offer }) =>
- !offer.stocks.some((stock) => {
+ !offer?.stocks?.some((stock) => {
if (!stock.beginningDatetime) {
return true
}
@@ -41,7 +41,7 @@ export const moviesOfferBuilder = (offersWithStocks: OfferResponseV2[] = []) =>
...rest,
offer: {
...offer,
- stocks: offer.stocks.filter((stock) => {
+ stocks: offer?.stocks?.filter((stock) => {
if (!stock.beginningDatetime) {
return false
}
@@ -57,22 +57,22 @@ export const moviesOfferBuilder = (offersWithStocks: OfferResponseV2[] = []) =>
}),
},
}))
- .filter(({ offer }) => offer.stocks.length > 0)
+ .filter(({ offer }) => offer?.stocks?.length > 0)
return builderObject
},
sortedByLast30DaysBooking: () => {
movieOffers = movieOffers.sort((a, b) => {
- const aValue = a.offer.last30DaysBookings
- const bValue = b.offer.last30DaysBookings
+ const aValue = a?.offer?.last30DaysBookings
+ const bValue = b?.offer?.last30DaysBookings
if (aValue === null || aValue === undefined) return 1
if (bValue === null || bValue === undefined) return -1
if (bValue === aValue) {
- const aEarliestDate = getEarliestDate(a.offer.stocks)
- const bEarliestDate = getEarliestDate(b.offer.stocks)
+ const aEarliestDate = getEarliestDate(a?.offer?.stocks)
+ const bEarliestDate = getEarliestDate(b?.offer?.stocks)
return aEarliestDate - bEarliestDate
}
@@ -86,34 +86,33 @@ export const moviesOfferBuilder = (offersWithStocks: OfferResponseV2[] = []) =>
sortedByDistance: (location: GeoCoordinates) => {
movieOffers = movieOffers.sort((a, b) => {
const aDistance = computeDistanceInMeters(
- a.offer.venue.coordinates.latitude ?? 0,
- a.offer.venue.coordinates.longitude ?? 0,
+ a.offer?.venue?.coordinates?.latitude ?? 0,
+ a.offer?.venue?.coordinates?.longitude ?? 0,
location?.latitude,
location?.longitude
)
const bDistance = computeDistanceInMeters(
- b.offer.venue.coordinates.latitude ?? 0,
- b.offer.venue.coordinates.longitude ?? 0,
+ b.offer?.venue?.coordinates?.latitude ?? 0,
+ b.offer?.venue?.coordinates?.longitude ?? 0,
location?.latitude,
location?.longitude
)
if (aDistance === bDistance) {
- const aEarliestDate = getEarliestDate(a.offer.stocks)
- const bEarliestDate = getEarliestDate(b.offer.stocks)
+ const aEarliestDate = getEarliestDate(a?.offer?.stocks)
+ const bEarliestDate = getEarliestDate(b?.offer?.stocks)
return aEarliestDate - bEarliestDate
}
return aDistance - bDistance
})
-
return builderObject
},
withScreeningsAfterNbDays: (nbDays: number) => {
movieOffers = movieOffers
.map(({ offer }) => {
- const filteredStocks = offer.stocks.filter((stock) => {
+ const filteredStocks = offer?.stocks?.filter((stock) => {
if (!stock.beginningDatetime) {
return false
}
@@ -121,21 +120,20 @@ export const moviesOfferBuilder = (offersWithStocks: OfferResponseV2[] = []) =>
return isDateNotWithinNextNbDays(new Date(), new Date(stock.beginningDatetime), nbDays)
})
return {
- nextDate: new Date(filteredStocks[0]?.beginningDatetime as string),
+ nextDate: new Date(filteredStocks?.[0]?.beginningDatetime as string),
offer: {
...offer,
stocks: filteredStocks,
},
}
})
- .filter(({ offer }) => offer.stocks.length > 0)
-
+ .filter(({ offer }) => offer?.stocks?.length > 0)
return builderObject
},
withoutScreeningsAfterNbDays: (nbDays: number) => {
movieOffers = movieOffers.filter(({ offer }) =>
- offer.stocks.some((stock) => {
+ offer?.stocks?.some((stock) => {
if (!stock.beginningDatetime) {
return false
}
@@ -198,7 +196,7 @@ const isDateBeforeToday = (referenceDate: Date, targetDate: Date) => {
}
const getNextDate = (offer: OfferResponseV2, date: Date) => {
- const dates = offer.stocks
+ const dates = offer?.stocks
.filter((stock) => stock.beginningDatetime)
.map((stock) => new Date(stock.beginningDatetime as string))
@@ -234,7 +232,7 @@ const getUpcomingDate = (offer: OfferResponseV2) => {
}
const getEarliestDate = (stocks: OfferStockResponse[]) => {
- return stocks.reduce((earliest, stock) => {
+ return stocks?.reduce((earliest, stock) => {
if (!stock.beginningDatetime) return earliest
const stockDate = new Date(stock.beginningDatetime).getTime()
return stockDate < earliest ? stockDate : earliest
diff --git a/src/features/offer/components/MoviesScreeningCalendar/types.ts b/src/features/offer/components/MoviesScreeningCalendar/types.ts
new file mode 100644
index 00000000000..df7c9bfb790
--- /dev/null
+++ b/src/features/offer/components/MoviesScreeningCalendar/types.ts
@@ -0,0 +1,6 @@
+import { OfferResponseV2 } from 'api/gen'
+
+export type MovieOffer = {
+ nextDate?: Date
+ offer: OfferResponseV2
+}
From f3b1467ec5c8a2d86dad6cdae42ea790c1790166 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?In=C3=A8s=20Mouandjo=20Lob=C3=A9?=
<158567429+imouandjolobe-pass@users.noreply.github.com>
Date: Tue, 21 Jan 2025 12:06:03 +0100
Subject: [PATCH 04/12] refactor: add missing property to type MovieOffer
---
.../components/MoviesScreeningCalendar/moviesOffer.builder.ts | 1 +
src/features/offer/components/MoviesScreeningCalendar/types.ts | 1 +
2 files changed, 2 insertions(+)
diff --git a/src/features/offer/components/MoviesScreeningCalendar/moviesOffer.builder.ts b/src/features/offer/components/MoviesScreeningCalendar/moviesOffer.builder.ts
index 7d871e2a7cc..c827ba53b87 100644
--- a/src/features/offer/components/MoviesScreeningCalendar/moviesOffer.builder.ts
+++ b/src/features/offer/components/MoviesScreeningCalendar/moviesOffer.builder.ts
@@ -125,6 +125,7 @@ export const moviesOfferBuilder = (offersWithStocks: OfferResponseV2[] = []) =>
...offer,
stocks: filteredStocks,
},
+ isUpcoming: false,
}
})
.filter(({ offer }) => offer?.stocks?.length > 0)
diff --git a/src/features/offer/components/MoviesScreeningCalendar/types.ts b/src/features/offer/components/MoviesScreeningCalendar/types.ts
index df7c9bfb790..1acb3afb50a 100644
--- a/src/features/offer/components/MoviesScreeningCalendar/types.ts
+++ b/src/features/offer/components/MoviesScreeningCalendar/types.ts
@@ -3,4 +3,5 @@ import { OfferResponseV2 } from 'api/gen'
export type MovieOffer = {
nextDate?: Date
offer: OfferResponseV2
+ isUpcoming: boolean
}
From 2b9330d2668d696c8198db2a218939a9a97d1a9d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?In=C3=A8s=20Mouandjo=20Lob=C3=A9?=
<158567429+imouandjolobe-pass@users.noreply.github.com>
Date: Wed, 22 Jan 2025 02:16:00 +0100
Subject: [PATCH 05/12] feat: handle logic
---
.../hook/getVenueMovieOffers.native.test.ts | 175 ++++++++++++++++++
.../hook/getVenueMovieOffers.ts | 41 ++++
.../moviesOffer.builder.test.ts | 54 ++++++
.../moviesOffer.builder.ts | 16 +-
4 files changed, 284 insertions(+), 2 deletions(-)
create mode 100644 src/features/offer/components/MoviesScreeningCalendar/hook/getVenueMovieOffers.native.test.ts
create mode 100644 src/features/offer/components/MoviesScreeningCalendar/hook/getVenueMovieOffers.ts
diff --git a/src/features/offer/components/MoviesScreeningCalendar/hook/getVenueMovieOffers.native.test.ts b/src/features/offer/components/MoviesScreeningCalendar/hook/getVenueMovieOffers.native.test.ts
new file mode 100644
index 00000000000..1aa20e25605
--- /dev/null
+++ b/src/features/offer/components/MoviesScreeningCalendar/hook/getVenueMovieOffers.native.test.ts
@@ -0,0 +1,175 @@
+import mockdate from 'mockdate'
+
+import { OffersStocksResponseV2 } from 'api/gen'
+import { mockBuilder } from 'tests/mockBuilder'
+import { reactQueryProviderHOC } from 'tests/reactQueryProviderHOC'
+import { renderHook } from 'tests/utils'
+
+import { getVenueMovieOffers } from './getVenueMovieOffers'
+
+const TODAY = new Date('2023-05-10')
+const BEFORE_SELECTED_DATE = '2023-05-07T00:00:00.000Z'
+const SELECTED_DATE = '2023-05-15T00:00:00.000Z'
+const AFTER_SELECTED_DATE = '2023-05-20T00:00:00.000Z'
+const AT_LEAST_15_DAYS_AFTER_SELECTED_DATE = '2023-06-20T00:00:00.000Z'
+
+describe('useVenueMovieOffers', () => {
+ beforeEach(() => {
+ mockdate.set(TODAY)
+ })
+
+ it('should sort offers by descending last30DaysBooking order', async () => {
+ const offers = {
+ offers: [
+ mockBuilder.offerResponseV2({
+ id: 1,
+ last30DaysBookings: 400,
+ stocks: [mockBuilder.offerStockResponse({ beginningDatetime: SELECTED_DATE })],
+ }),
+ mockBuilder.offerResponseV2({
+ id: 2,
+ last30DaysBookings: 200,
+ stocks: [mockBuilder.offerStockResponse({ beginningDatetime: SELECTED_DATE })],
+ }),
+ mockBuilder.offerResponseV2({
+ id: 3,
+ last30DaysBookings: 300,
+ stocks: [mockBuilder.offerStockResponse({ beginningDatetime: SELECTED_DATE })],
+ }),
+ ],
+ }
+ const { result } = renderUseVenueMovieOffers(new Date(SELECTED_DATE), offers)
+
+ expect(result.current.venueMovieOffers).toEqual([
+ expect.objectContaining({
+ offer: expect.objectContaining({ last30DaysBookings: 400 }),
+ }),
+ expect.objectContaining({
+ offer: expect.objectContaining({ last30DaysBookings: 300 }),
+ }),
+ expect.objectContaining({
+ offer: expect.objectContaining({ last30DaysBookings: 200 }),
+ }),
+ ])
+ })
+
+ it('should return movie offers of the selected date', async () => {
+ const offers = {
+ offers: [
+ mockBuilder.offerResponseV2({
+ id: 1,
+ stocks: [mockBuilder.offerStockResponse({ beginningDatetime: SELECTED_DATE })],
+ }),
+ mockBuilder.offerResponseV2({
+ id: 2,
+ stocks: [mockBuilder.offerStockResponse({ beginningDatetime: BEFORE_SELECTED_DATE })],
+ }),
+ ],
+ }
+
+ const { result } = renderUseVenueMovieOffers(new Date(SELECTED_DATE), offers)
+
+ expect(result.current.venueMovieOffers).toHaveLength(1)
+ expect(result.current.venueMovieOffers[0]?.offer.id).toBe(1)
+ })
+
+ it('should return next movie offers of a given date', async () => {
+ const offers = {
+ offers: [
+ mockBuilder.offerResponseV2({
+ id: 1,
+ stocks: [
+ mockBuilder.offerStockResponse({
+ beginningDatetime: AFTER_SELECTED_DATE,
+ }),
+ ],
+ }),
+ mockBuilder.offerResponseV2({
+ id: 2,
+ stocks: [mockBuilder.offerStockResponse({ beginningDatetime: BEFORE_SELECTED_DATE })],
+ }),
+ ],
+ }
+
+ const { result } = renderUseVenueMovieOffers(new Date(SELECTED_DATE), offers)
+
+ expect(result.current.venueMovieOffers).toHaveLength(1)
+ expect(result.current.venueMovieOffers[0]?.offer.id).toBe(1)
+ })
+
+ it('should return empty array when there are no movies on a given day or after', async () => {
+ const offers = {
+ offers: [
+ mockBuilder.offerResponseV2({
+ id: 1,
+ stocks: [mockBuilder.offerStockResponse({ beginningDatetime: BEFORE_SELECTED_DATE })],
+ }),
+ ],
+ }
+
+ const { result } = renderUseVenueMovieOffers(new Date(SELECTED_DATE), offers)
+
+ expect(result.current.venueMovieOffers).toHaveLength(0)
+ })
+
+ it('should not return movies with screenings after 15 days when other movies have screenings within 15 days', async () => {
+ const offers = {
+ offers: [
+ mockBuilder.offerResponseV2({
+ id: 1,
+ stocks: [
+ mockBuilder.offerStockResponse({
+ beginningDatetime: SELECTED_DATE,
+ }),
+ ],
+ }),
+ mockBuilder.offerResponseV2({
+ id: 2,
+ stocks: [
+ mockBuilder.offerStockResponse({
+ beginningDatetime: AT_LEAST_15_DAYS_AFTER_SELECTED_DATE,
+ }),
+ ],
+ }),
+ ],
+ }
+
+ const { result } = renderUseVenueMovieOffers(new Date(SELECTED_DATE), offers)
+
+ expect(result.current.venueMovieOffers).toHaveLength(1)
+ expect(result.current.venueMovieOffers[0]?.offer.id).toBe(1)
+ })
+
+ it("should only return movie with screenings after 15 days when other movies don't have screenings within 15 days", async () => {
+ const offers = {
+ offers: [
+ mockBuilder.offerResponseV2({
+ id: 1,
+ stocks: [
+ mockBuilder.offerStockResponse({
+ beginningDatetime: BEFORE_SELECTED_DATE,
+ }),
+ ],
+ }),
+ mockBuilder.offerResponseV2({
+ id: 2,
+ stocks: [
+ mockBuilder.offerStockResponse({
+ beginningDatetime: AT_LEAST_15_DAYS_AFTER_SELECTED_DATE,
+ }),
+ ],
+ }),
+ ],
+ }
+
+ const { result } = renderUseVenueMovieOffers(new Date(SELECTED_DATE), offers)
+
+ expect(result.current.venueMovieOffers).toHaveLength(1)
+ expect(result.current.venueMovieOffers[0]?.offer.id).toBe(2)
+ })
+})
+
+const renderUseVenueMovieOffers = (selectedDate: Date, offers: OffersStocksResponseV2) =>
+ renderHook(() => getVenueMovieOffers(selectedDate, offers), {
+ wrapper: ({ children }) => reactQueryProviderHOC(children),
+ })
diff --git a/src/features/offer/components/MoviesScreeningCalendar/hook/getVenueMovieOffers.ts b/src/features/offer/components/MoviesScreeningCalendar/hook/getVenueMovieOffers.ts
new file mode 100644
index 00000000000..c1a3ebf53f3
--- /dev/null
+++ b/src/features/offer/components/MoviesScreeningCalendar/hook/getVenueMovieOffers.ts
@@ -0,0 +1,41 @@
+import { OffersStocksResponseV2, SubcategoryIdEnum } from 'api/gen'
+import { moviesOfferBuilder } from 'features/offer/components/MoviesScreeningCalendar/moviesOffer.builder'
+
+export const getVenueMovieOffers = (
+ selectedDate: Date,
+ offersWithStocks: OffersStocksResponseV2 | undefined
+) => {
+ const movieOffers = offersWithStocks?.offers.filter(
+ (offer) => offer.subcategoryId === SubcategoryIdEnum.SEANCE_CINE
+ )
+
+ const dayOffers = moviesOfferBuilder(movieOffers)
+ .withScreeningsOnDay(selectedDate)
+ .sortedByLast30DaysBooking()
+ .build()
+
+ const nextOffers = moviesOfferBuilder(movieOffers)
+ .withoutScreeningsOnDay(selectedDate)
+ .withoutScreeningsAfterNbDays(15)
+ .withNextScreeningFromDate(selectedDate)
+ .sortedByLast30DaysBooking()
+ .build()
+
+ const after15DaysOffers = moviesOfferBuilder(movieOffers)
+ .withScreeningsAfterNbDays(15)
+ .sortedByLast30DaysBooking()
+ .sortedByDate()
+ .build()
+
+ const hasStocksOnlyAfter15Days =
+ !dayOffers.length && !nextOffers.length && after15DaysOffers.length > 0
+
+ const venueMovieOffers = hasStocksOnlyAfter15Days
+ ? after15DaysOffers
+ : [...dayOffers, ...nextOffers]
+
+ return {
+ venueMovieOffers,
+ hasStocksOnlyAfter15Days,
+ }
+}
diff --git a/src/features/offer/components/MoviesScreeningCalendar/moviesOffer.builder.test.ts b/src/features/offer/components/MoviesScreeningCalendar/moviesOffer.builder.test.ts
index 1e41956bed7..a9c20a2f68c 100644
--- a/src/features/offer/components/MoviesScreeningCalendar/moviesOffer.builder.test.ts
+++ b/src/features/offer/components/MoviesScreeningCalendar/moviesOffer.builder.test.ts
@@ -34,6 +34,28 @@ describe('moviesOfferBuilder', () => {
})
})
+ describe('withoutMoviesOnDay', () => {
+ it('should not return movies of the selected day', () => {
+ const offer1 = mockBuilder.offerResponseV2({
+ stocks: [mockBuilder.offerStockResponse({ beginningDatetime: selectedDate.toString() })],
+ })
+ const offer2 = mockBuilder.offerResponseV2({
+ stocks: [
+ mockBuilder.offerStockResponse({
+ beginningDatetime: addDays(selectedDate, 1).toString(),
+ }),
+ ],
+ })
+
+ const result = moviesOfferBuilder([offer1, offer2])
+ .withoutScreeningsOnDay(selectedDate)
+ .buildOfferResponse()
+
+ expect(result).toHaveLength(1)
+ expect(result[0]?.id).toBe(offer2.id)
+ })
+ })
+
describe('sortedByLast30DaysBooking', () => {
it('should sort offers by last 30 days bookings', () => {
const offer1 = mockBuilder.offerResponseV2({ last30DaysBookings: 10 })
@@ -128,6 +150,38 @@ describe('moviesOfferBuilder', () => {
})
})
+ describe('sortedByDate', () => {
+ it('should sort offers by earliest next date', () => {
+ const offer1 = mockBuilder.offerResponseV2({
+ stocks: [
+ mockBuilder.offerStockResponse({
+ beginningDatetime: addDays(selectedDate, 3).toString(),
+ }),
+ ],
+ })
+ const offer2 = mockBuilder.offerResponseV2({
+ stocks: [
+ mockBuilder.offerStockResponse({
+ beginningDatetime: addDays(selectedDate, 4).toString(),
+ }),
+ ],
+ })
+ const offer3 = mockBuilder.offerResponseV2({
+ stocks: [
+ mockBuilder.offerStockResponse({
+ beginningDatetime: addDays(selectedDate, 2).toString(),
+ }),
+ ],
+ })
+
+ const result = moviesOfferBuilder([offer1, offer2, offer3])
+ .sortedByDate()
+ .buildOfferResponse()
+
+ expect(result.map((o) => o.id)).toEqual([offer3.id, offer1.id, offer2.id])
+ })
+ })
+
describe('withoutMoviesAfterNbDays', () => {
it('should filter out movies within the specified range of days', () => {
const offer1 = mockBuilder.offerResponseV2({
diff --git a/src/features/offer/components/MoviesScreeningCalendar/moviesOffer.builder.ts b/src/features/offer/components/MoviesScreeningCalendar/moviesOffer.builder.ts
index c827ba53b87..6c5903c95e9 100644
--- a/src/features/offer/components/MoviesScreeningCalendar/moviesOffer.builder.ts
+++ b/src/features/offer/components/MoviesScreeningCalendar/moviesOffer.builder.ts
@@ -26,8 +26,12 @@ export const moviesOfferBuilder = (offersWithStocks: OfferResponseV2[] = []) =>
if (!stock.beginningDatetime) {
return true
}
- if (isSameDay(new Date(stock.beginningDatetime), selectedDate)) {
- return isAfter(new Date(stock.beginningDatetime), selectedDate)
+ const isSameDate = isSameDay(new Date(stock.beginningDatetime), selectedDate)
+ if (
+ isSameDate ||
+ (isSameDate && isAfter(new Date(stock.beginningDatetime), selectedDate))
+ ) {
+ return true
}
return false
})
@@ -109,6 +113,14 @@ export const moviesOfferBuilder = (offersWithStocks: OfferResponseV2[] = []) =>
return builderObject
},
+ sortedByDate: () => {
+ movieOffers = movieOffers.sort(
+ (a, b) => new Date(a.nextDate ?? 0).getTime() - new Date(b.nextDate ?? 0).getTime()
+ )
+
+ return builderObject
+ },
+
withScreeningsAfterNbDays: (nbDays: number) => {
movieOffers = movieOffers
.map(({ offer }) => {
From 06c324f17b5e7a8f313f2a7368e8e6294d5eb7ef Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?In=C3=A8s=20Mouandjo=20Lob=C3=A9?=
<158567429+imouandjolobe-pass@users.noreply.github.com>
Date: Wed, 22 Jan 2025 02:17:08 +0100
Subject: [PATCH 06/12] feat: handle calendar display
---
.../MoviesScreeningCalendar/VenueCalendar.tsx | 24 +++++++++++++++----
1 file changed, 19 insertions(+), 5 deletions(-)
diff --git a/src/features/offer/components/MoviesScreeningCalendar/VenueCalendar.tsx b/src/features/offer/components/MoviesScreeningCalendar/VenueCalendar.tsx
index 17bf47a84b0..411c5af269b 100644
--- a/src/features/offer/components/MoviesScreeningCalendar/VenueCalendar.tsx
+++ b/src/features/offer/components/MoviesScreeningCalendar/VenueCalendar.tsx
@@ -2,7 +2,12 @@ import React, { FunctionComponent, useCallback } from 'react'
import { View } from 'react-native'
import styled from 'styled-components/native'
-import { useMoviesScreeningsList } from 'features/offer/components/MoviesScreeningCalendar/hook/useMoviesScreeningsList'
+import { useOffersStocks } from 'features/offer/api/useOffersStocks'
+import { getVenueMovieOffers } from 'features/offer/components/MoviesScreeningCalendar/hook/getVenueMovieOffers'
+import {
+ useDisplayCalendar,
+ useMovieCalendar,
+} from 'features/offer/components/MoviesScreeningCalendar/MovieCalendarContext'
import { MovieOfferTile } from 'features/offer/components/MoviesScreeningCalendar/MovieOfferTile'
import { VenueOffers } from 'features/venue/types'
import { Spacer } from 'ui/theme'
@@ -13,20 +18,29 @@ type Props = {
}
export const VenueCalendar: FunctionComponent = ({ venueOffers, offerIds }) => {
- const { moviesOffers } = useMoviesScreeningsList(offerIds)
+ const { data: offersWithStocks } = useOffersStocks({ offerIds })
+
+ const { selectedDate } = useMovieCalendar()
+
+ const { venueMovieOffers, hasStocksOnlyAfter15Days } = getVenueMovieOffers(
+ selectedDate,
+ offersWithStocks
+ )
+
+ useDisplayCalendar(!hasStocksOnlyAfter15Days)
const getIsLast = useCallback(
(index: number) => {
- const length = moviesOffers.length ?? 0
+ const length = venueMovieOffers?.length ?? 0
return index === length - 1
},
- [moviesOffers.length]
+ [venueMovieOffers?.length]
)
return (
- {moviesOffers.map((movie, index) => (
+ {venueMovieOffers?.map((movie, index) => (
Date: Wed, 22 Jan 2025 02:17:52 +0100
Subject: [PATCH 07/12] refactor: remove some files
---
.../filterOffersStocksByDate.test.ts | 112 -----------
.../filterOffersStocksByDate.ts | 7 -
.../getNextMoviesByDate.test.ts | 187 ------------------
.../getNextMoviesByDate.ts | 19 --
.../useMoviesScreeningsList.native.test.ts | 82 --------
.../hook/useMoviesScreeningsList.ts | 32 ---
6 files changed, 439 deletions(-)
delete mode 100644 src/features/offer/components/MoviesScreeningCalendar/filterOffersStocksByDate.test.ts
delete mode 100644 src/features/offer/components/MoviesScreeningCalendar/filterOffersStocksByDate.ts
delete mode 100644 src/features/offer/components/MoviesScreeningCalendar/getNextMoviesByDate.test.ts
delete mode 100644 src/features/offer/components/MoviesScreeningCalendar/getNextMoviesByDate.ts
delete mode 100644 src/features/offer/components/MoviesScreeningCalendar/hook/useMoviesScreeningsList.native.test.ts
delete mode 100644 src/features/offer/components/MoviesScreeningCalendar/hook/useMoviesScreeningsList.ts
diff --git a/src/features/offer/components/MoviesScreeningCalendar/filterOffersStocksByDate.test.ts b/src/features/offer/components/MoviesScreeningCalendar/filterOffersStocksByDate.test.ts
deleted file mode 100644
index dba79831c59..00000000000
--- a/src/features/offer/components/MoviesScreeningCalendar/filterOffersStocksByDate.test.ts
+++ /dev/null
@@ -1,112 +0,0 @@
-import mockdate from 'mockdate'
-
-import { OfferResponseV2, OfferStockResponse } from 'api/gen'
-import { MovieOffer } from 'features/offer/components/MoviesScreeningCalendar/getNextMoviesByDate'
-import { dateBuilder, mockBuilder } from 'tests/mockBuilder'
-
-import { filterOffersStocksByDate } from './filterOffersStocksByDate'
-
-const DATE_WITH_STOCK = dateBuilder().withDay(8).toString()
-const DATE_WITH_NO_STOCK = dateBuilder().withDay(5).toString()
-const AVAILABLE_STOCK_ID = 11
-
-const STOCKS = [
- mockBuilder.offerStockResponse({
- id: AVAILABLE_STOCK_ID,
- beginningDatetime: DATE_WITH_STOCK,
- }),
-]
-
-const offers: OfferResponseV2[] = [
- mockBuilder.offerResponseV2({
- id: 1,
- stocks: STOCKS,
- }),
-]
-
-describe('filterOffersStocksByDate', () => {
- it('should return all movies with a stock available at a specific date', () => {
- const filteredOffersStocks = filterOffersStocksByDate(offers, new Date(DATE_WITH_STOCK))
- const expectedStock = getStockById(filteredOffersStocks, AVAILABLE_STOCK_ID)
-
- expect(expectedStock).toBeDefined()
- })
-
- it('should not return movies if a stock is unavailable at a specific date', () => {
- const filteredOffersStocksByDate = filterOffersStocksByDate(
- offers,
- new Date(DATE_WITH_NO_STOCK)
- )
-
- const expectedStock = getStockById(filteredOffersStocksByDate, AVAILABLE_STOCK_ID)
-
- expect(expectedStock).not.toBeDefined()
- })
-
- it('should sort the stocks by descending last30DaysBookings order', () => {
- const offers: OfferResponseV2[] = [
- mockBuilder.offerResponseV2({
- id: 1,
- last30DaysBookings: 100,
- stocks: STOCKS,
- }),
- mockBuilder.offerResponseV2({
- id: 3,
- last30DaysBookings: 300,
- stocks: STOCKS,
- }),
- mockBuilder.offerResponseV2({
- id: 2,
- last30DaysBookings: 200,
- stocks: STOCKS,
- }),
- mockBuilder.offerResponseV2({
- id: 4,
- last30DaysBookings: 400,
- stocks: STOCKS,
- }),
- ]
-
- const filteredOffersStocksByDate = filterOffersStocksByDate(offers, new Date(DATE_WITH_STOCK))
-
- expect(filteredOffersStocksByDate[0]?.offer?.last30DaysBookings).toBe(400)
- expect(filteredOffersStocksByDate[1]?.offer?.last30DaysBookings).toBe(300)
- expect(filteredOffersStocksByDate[2]?.offer.last30DaysBookings).toBe(200)
- expect(filteredOffersStocksByDate[3]?.offer.last30DaysBookings).toBe(100)
- })
-})
-
-it('should not return movies when there is no more screening until the end of the day', () => {
- const NOW = dateBuilder().withHours(16).toDate()
- mockdate.set(NOW)
-
- const BEFORE_NOW = dateBuilder().withHours(14).toString()
- const AFTER_NOW = dateBuilder().withHours(18).toString()
-
- const STOCK_BEFORE_NOW = mockBuilder.offerStockResponse({
- beginningDatetime: BEFORE_NOW,
- })
- const STOCK_AFTER_NOW = mockBuilder.offerStockResponse({
- beginningDatetime: AFTER_NOW,
- })
-
- const offersContainingAnOfferAfterNow = filterOffersStocksByDate(
- [mockBuilder.offerResponseV2({ stocks: [STOCK_BEFORE_NOW, STOCK_AFTER_NOW] })],
- NOW
- )
-
- expect(offersContainingAnOfferAfterNow).toHaveLength(1)
-
- const offersContainingNoOfferAfterNow = filterOffersStocksByDate(
- [mockBuilder.offerResponseV2({ stocks: [STOCK_BEFORE_NOW] })],
- NOW
- )
-
- expect(offersContainingNoOfferAfterNow).toHaveLength(0)
-})
-
-const getStockById = (movieOffer: MovieOffer[], id: number) => {
- return movieOffer.find(({ offer }) =>
- offer?.stocks.find((stock: OfferStockResponse) => stock.id === id)
- )
-}
diff --git a/src/features/offer/components/MoviesScreeningCalendar/filterOffersStocksByDate.ts b/src/features/offer/components/MoviesScreeningCalendar/filterOffersStocksByDate.ts
deleted file mode 100644
index c746ac9718f..00000000000
--- a/src/features/offer/components/MoviesScreeningCalendar/filterOffersStocksByDate.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { OfferResponseV2 } from 'api/gen'
-import { MovieOffer } from 'features/offer/components/MoviesScreeningCalendar/getNextMoviesByDate'
-import { moviesOfferBuilder } from 'features/offer/components/MoviesScreeningCalendar/moviesOffer.builder'
-
-export const filterOffersStocksByDate = (offers: OfferResponseV2[], date: Date): MovieOffer[] => {
- return moviesOfferBuilder(offers).withScreeningsOnDay(date).sortedByLast30DaysBooking().build()
-}
diff --git a/src/features/offer/components/MoviesScreeningCalendar/getNextMoviesByDate.test.ts b/src/features/offer/components/MoviesScreeningCalendar/getNextMoviesByDate.test.ts
deleted file mode 100644
index 0fc1ec54b2c..00000000000
--- a/src/features/offer/components/MoviesScreeningCalendar/getNextMoviesByDate.test.ts
+++ /dev/null
@@ -1,187 +0,0 @@
-import mockdate from 'mockdate'
-
-import { OfferResponseV2 } from 'api/gen'
-import { getNextMoviesByDate } from 'features/offer/components/MoviesScreeningCalendar/getNextMoviesByDate'
-import { dateBuilder, mockBuilder } from 'tests/mockBuilder'
-
-const SELECTED_DATE = dateBuilder().withDay(5).toString()
-const SELECTED_DATE_PLUS_1 = dateBuilder().withDay(6).toString()
-
-const TODAY = dateBuilder().withDay(1).toString()
-const TOMORROW = dateBuilder().withDay(2).toString()
-const AFTER_TOMORROW = dateBuilder().withDay(3).toString()
-const AFTER_15_DAYS = dateBuilder().withDay(17).toString()
-
-const TOMORROW_STOCKS = [
- mockBuilder.offerStockResponse({ beginningDatetime: TOMORROW }),
- mockBuilder.offerStockResponse({ beginningDatetime: AFTER_TOMORROW }),
-]
-const AFTER_15_DAYS_STOCKS = [mockBuilder.offerStockResponse({ beginningDatetime: AFTER_15_DAYS })]
-
-mockdate.set(TODAY)
-
-describe('getNextMoviesByDate', () => {
- it('should return next movies', () => {
- const offerStocks: OfferResponseV2[] = [
- mockBuilder.offerResponseV2({ id: 1, stocks: TOMORROW_STOCKS }),
- mockBuilder.offerResponseV2({ id: 2, stocks: TOMORROW_STOCKS }),
- mockBuilder.offerResponseV2({ id: 3, stocks: TOMORROW_STOCKS }),
- ]
-
- const nextMovies = getNextMoviesByDate(offerStocks, new Date(TODAY))
-
- expect(nextMovies).toHaveLength(offerStocks.length)
- })
-
- it('should not return movies with only stocks after 15 days', () => {
- const offerStocks: OfferResponseV2[] = [
- mockBuilder.offerResponseV2({ id: 1, stocks: TOMORROW_STOCKS }),
- mockBuilder.offerResponseV2({ id: 2, stocks: AFTER_15_DAYS_STOCKS }),
- ]
-
- const nextMovies = getNextMoviesByDate(offerStocks, new Date(TODAY))
-
- expect(nextMovies).toHaveLength(1)
- expect(nextMovies[0]?.offer.id).toBe(1)
- })
-
- it('should return movies with stocks after 15 days only if they have also stocks in the next 15 days', () => {
- const offerStocks: OfferResponseV2[] = [
- mockBuilder.offerResponseV2({
- stocks: [
- mockBuilder.offerStockResponse({ beginningDatetime: TOMORROW }),
- mockBuilder.offerStockResponse({ beginningDatetime: AFTER_15_DAYS }),
- mockBuilder.offerStockResponse({ beginningDatetime: AFTER_15_DAYS }),
- ],
- }),
- ]
- const nextMovies = getNextMoviesByDate(offerStocks, new Date(TODAY))
-
- expect(nextMovies).toHaveLength(1)
- })
-
- it('should return the movies with their first upcoming date', () => {
- const offerStocks: OfferResponseV2[] = [
- mockBuilder.offerResponseV2({
- stocks: [
- mockBuilder.offerStockResponse({ beginningDatetime: AFTER_TOMORROW }),
- mockBuilder.offerStockResponse({ beginningDatetime: TOMORROW }),
- ],
- }),
- ]
-
- const nextMovies = getNextMoviesByDate(offerStocks, new Date(TODAY))
-
- expect(nextMovies[0]?.nextDate).toStrictEqual(new Date(TOMORROW))
- })
-
- it('should return the movies with their first upcoming date when the selected day is different than today', () => {
- const offerStocks: OfferResponseV2[] = [
- mockBuilder.offerResponseV2({
- stocks: [
- mockBuilder.offerStockResponse({ beginningDatetime: TOMORROW }),
- mockBuilder.offerStockResponse({ beginningDatetime: AFTER_TOMORROW }),
- mockBuilder.offerStockResponse({ beginningDatetime: SELECTED_DATE_PLUS_1 }),
- ],
- }),
- ]
-
- const nextMovies = getNextMoviesByDate(offerStocks, new Date(SELECTED_DATE))
-
- expect(nextMovies[0]?.nextDate).toStrictEqual(new Date(SELECTED_DATE_PLUS_1))
- })
-
- it('should return the movies with the first upcoming stock if there is none after the selected date within the 15 next days from today', () => {
- const offerStocks: OfferResponseV2[] = [
- mockBuilder.offerResponseV2({
- stocks: [
- mockBuilder.offerStockResponse({ beginningDatetime: TOMORROW }),
- mockBuilder.offerStockResponse({ beginningDatetime: AFTER_TOMORROW }),
- ],
- }),
- ]
-
- const nextMovies = getNextMoviesByDate(offerStocks, new Date(SELECTED_DATE))
-
- expect(nextMovies[0]?.nextDate).toStrictEqual(new Date(TOMORROW))
- })
-
- it('should return the movies with the no stock available from now', () => {
- const NOW = dateBuilder().withHours(16).toDate()
- mockdate.set(NOW)
-
- const BEFORE_NOW = dateBuilder().withHours(14).toString()
- const AFTER_NOW = dateBuilder().withHours(18).toString()
-
- const STOCK_BEFORE_NOW = mockBuilder.offerStockResponse({ beginningDatetime: BEFORE_NOW })
- const STOCK_AFTER_NOW = mockBuilder.offerStockResponse({ beginningDatetime: AFTER_NOW })
-
- const offersContainingAnOfferAfterNow = getNextMoviesByDate(
- [mockBuilder.offerResponseV2({ stocks: [STOCK_BEFORE_NOW, STOCK_AFTER_NOW] })],
- NOW
- )
-
- expect(offersContainingAnOfferAfterNow).toHaveLength(0)
-
- const offersContainingNoOfferAfterNow = getNextMoviesByDate(
- [mockBuilder.offerResponseV2({ stocks: [STOCK_BEFORE_NOW, ...TOMORROW_STOCKS] })],
- NOW
- )
-
- expect(offersContainingNoOfferAfterNow).toHaveLength(1)
- })
-
- it('should not return a movie if there is no stock after the selected date', () => {
- const NOW = dateBuilder().withHours(16).toDate()
- mockdate.set(NOW)
-
- const BEFORE_NOW = dateBuilder().withHours(14).toString()
-
- const STOCK_BEFORE_NOW = mockBuilder.offerStockResponse({ beginningDatetime: BEFORE_NOW })
-
- const offersContainingNoOfferAfterNow = getNextMoviesByDate(
- [mockBuilder.offerResponseV2({ stocks: [STOCK_BEFORE_NOW] })],
- NOW
- )
-
- expect(offersContainingNoOfferAfterNow).toHaveLength(0)
- })
-
- it('should sort the offers by descending last30DaysBookings order', () => {
- const offers: OfferResponseV2[] = [
- mockBuilder.offerResponseV2({ last30DaysBookings: 100, stocks: TOMORROW_STOCKS }),
- mockBuilder.offerResponseV2({ last30DaysBookings: 300, stocks: TOMORROW_STOCKS }),
- mockBuilder.offerResponseV2({ last30DaysBookings: 200, stocks: TOMORROW_STOCKS }),
- mockBuilder.offerResponseV2({ last30DaysBookings: 400, stocks: TOMORROW_STOCKS }),
- ]
-
- const filteredOffersStocks = getNextMoviesByDate(offers, new Date(TODAY))
-
- expect(filteredOffersStocks[0]?.offer?.last30DaysBookings).toBe(400)
- expect(filteredOffersStocks[1]?.offer?.last30DaysBookings).toBe(300)
- expect(filteredOffersStocks[2]?.offer.last30DaysBookings).toBe(200)
- expect(filteredOffersStocks[3]?.offer.last30DaysBookings).toBe(100)
- })
-
- it('should sort the offers by ascending beginingDateTime order if 2 offers have the same last30DaysBooking value', () => {
- const EARLIER_BEGINNING_DATE_TIME = dateBuilder().withDay(2).withHours(1).toString()
- const LATER_BEGINNING_DATE_TIME = dateBuilder().withDay(2).withHours(2).toString()
-
- const EARLIER_STOCKS = [
- mockBuilder.offerStockResponse({ beginningDatetime: EARLIER_BEGINNING_DATE_TIME }),
- ]
- const LATER_STOCKS = [
- mockBuilder.offerStockResponse({ beginningDatetime: LATER_BEGINNING_DATE_TIME }),
- ]
-
- const offers: OfferResponseV2[] = [
- mockBuilder.offerResponseV2({ id: 1, last30DaysBookings: 300, stocks: LATER_STOCKS }),
- mockBuilder.offerResponseV2({ id: 2, last30DaysBookings: 300, stocks: EARLIER_STOCKS }),
- ]
-
- const filteredOffersStocks = getNextMoviesByDate(offers, new Date(TODAY))
-
- expect(filteredOffersStocks[0]?.offer?.id).toBe(2)
- expect(filteredOffersStocks[1]?.offer?.id).toBe(1)
- })
-})
diff --git a/src/features/offer/components/MoviesScreeningCalendar/getNextMoviesByDate.ts b/src/features/offer/components/MoviesScreeningCalendar/getNextMoviesByDate.ts
deleted file mode 100644
index a57cce2a630..00000000000
--- a/src/features/offer/components/MoviesScreeningCalendar/getNextMoviesByDate.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { OfferResponseV2 } from 'api/gen'
-import { moviesOfferBuilder } from 'features/offer/components/MoviesScreeningCalendar/moviesOffer.builder'
-
-export type MovieOffer = {
- nextDate?: Date
- offer: OfferResponseV2
-}
-
-export const getNextMoviesByDate = (
- offersWithStocks: OfferResponseV2[],
- date: Date
-): MovieOffer[] => {
- return moviesOfferBuilder(offersWithStocks)
- .withoutScreeningsAfterNbDays(15)
- .withoutScreeningsOnDay(date)
- .withNextScreeningFromDate(date)
- .sortedByLast30DaysBooking()
- .build()
-}
diff --git a/src/features/offer/components/MoviesScreeningCalendar/hook/useMoviesScreeningsList.native.test.ts b/src/features/offer/components/MoviesScreeningCalendar/hook/useMoviesScreeningsList.native.test.ts
deleted file mode 100644
index ec4ca81c596..00000000000
--- a/src/features/offer/components/MoviesScreeningCalendar/hook/useMoviesScreeningsList.native.test.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-import { SubcategoryIdEnum } from 'api/gen'
-import * as getStocksByOfferIdsModule from 'features/offer/api/getStocksByOfferIds'
-import * as useMovieCalendarModule from 'features/offer/components/MoviesScreeningCalendar/MovieCalendarContext'
-import { mockBuilder } from 'tests/mockBuilder'
-import { reactQueryProviderHOC } from 'tests/reactQueryProviderHOC'
-import { act, renderHook } from 'tests/utils'
-
-import { useMoviesScreeningsList } from './useMoviesScreeningsList'
-
-jest.mock('libs/network/NetInfoWrapper')
-
-const getStocksByOfferIdsModuleSpy = jest.spyOn(getStocksByOfferIdsModule, 'getStocksByOfferIds')
-
-describe('useMoviesScreeningsList', () => {
- const mockSelectedDate = new Date('2023-05-15')
- const mockOfferIds = [1, 2, 3]
-
- beforeEach(() => {
- jest.spyOn(useMovieCalendarModule, 'useMovieCalendar').mockReturnValue({
- selectedDate: mockSelectedDate,
- goToDate: jest.fn(),
- displayCalendar: jest.fn(),
- dates: [],
- disableDates: jest.fn(),
- displayDates: jest.fn(),
- })
- })
-
- it('should return filtered movie offers', async () => {
- const mockOffers = [
- mockBuilder.offerResponseV2({
- id: 1,
- subcategoryId: SubcategoryIdEnum.SEANCE_CINE,
- stocks: [
- mockBuilder.offerStockResponse({
- beginningDatetime: '2023-05-15T10:00:00Z',
- }),
- ],
- }),
- mockBuilder.offerResponseV2({
- id: 2,
- subcategoryId: SubcategoryIdEnum.SEANCE_CINE,
- stocks: [
- mockBuilder.offerStockResponse({
- beginningDatetime: '2023-05-16T10:00:00Z',
- }),
- ],
- }),
- mockBuilder.offerResponseV2({
- id: 3,
- subcategoryId: SubcategoryIdEnum.LIVRE_PAPIER,
- stocks: [
- mockBuilder.offerStockResponse({
- beginningDatetime: '2023-05-15T10:00:00Z',
- }),
- ],
- }),
- ]
-
- getStocksByOfferIdsModuleSpy.mockResolvedValueOnce({ offers: mockOffers })
-
- const { result } = renderHook(() => useMoviesScreeningsList(mockOfferIds), {
- wrapper: ({ children }) => reactQueryProviderHOC(children),
- })
-
- await act(async () => {})
-
- expect(result.current.moviesOffers).toStrictEqual([{ isUpcoming: false, offer: mockOffers[0] }])
- })
-
- it('should return an empty array when no offers match the criteria', async () => {
- getStocksByOfferIdsModuleSpy.mockResolvedValueOnce({ offers: [] })
-
- const { result } = renderHook(() => useMoviesScreeningsList(mockOfferIds), {
- wrapper: ({ children }) => reactQueryProviderHOC(children),
- })
-
- await act(async () => {})
-
- expect(result.current.moviesOffers).toHaveLength(0)
- })
-})
diff --git a/src/features/offer/components/MoviesScreeningCalendar/hook/useMoviesScreeningsList.ts b/src/features/offer/components/MoviesScreeningCalendar/hook/useMoviesScreeningsList.ts
deleted file mode 100644
index 9e23e6bcc63..00000000000
--- a/src/features/offer/components/MoviesScreeningCalendar/hook/useMoviesScreeningsList.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { useMemo } from 'react'
-
-import { SubcategoryIdEnum } from 'api/gen'
-import { useOffersStocks } from 'features/offer/api/useOffersStocks'
-import { filterOffersStocksByDate } from 'features/offer/components/MoviesScreeningCalendar/filterOffersStocksByDate'
-import {
- MovieOffer,
- getNextMoviesByDate,
-} from 'features/offer/components/MoviesScreeningCalendar/getNextMoviesByDate'
-import { useMovieCalendar } from 'features/offer/components/MoviesScreeningCalendar/MovieCalendarContext'
-
-export const useMoviesScreeningsList = (offerIds: number[]) => {
- const { selectedDate } = useMovieCalendar()
- const { data: offersWithStocks } = useOffersStocks({ offerIds })
-
- const moviesOffers: MovieOffer[] = useMemo(() => {
- const filteredOffersWithStocks = filterOffersStocksByDate(
- offersWithStocks?.offers || [],
- selectedDate
- )
-
- const nextScreeningOffers = getNextMoviesByDate(offersWithStocks?.offers || [], selectedDate)
-
- return [...filteredOffersWithStocks, ...nextScreeningOffers]
- }, [offersWithStocks?.offers, selectedDate]).filter(
- (offer) => offer.offer.subcategoryId === SubcategoryIdEnum.SEANCE_CINE
- )
-
- return {
- moviesOffers,
- }
-}
From 4f681a9892f18754c3c3e19c4d02974df3c622ce Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?In=C3=A8s=20Mouandjo=20Lob=C3=A9?=
<158567429+imouandjolobe-pass@users.noreply.github.com>
Date: Wed, 22 Jan 2025 03:06:46 +0100
Subject: [PATCH 08/12] clean: remove spacer + use setFF in test
---
.../components/VenueOffers/VenueMovies.tsx | 16 +++++++-------
.../VenueOffers/VenueOffers.native.test.tsx | 21 +++++++++----------
2 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/src/features/venue/components/VenueOffers/VenueMovies.tsx b/src/features/venue/components/VenueOffers/VenueMovies.tsx
index 424a0ba3989..8c6c08f76e0 100644
--- a/src/features/venue/components/VenueOffers/VenueMovies.tsx
+++ b/src/features/venue/components/VenueOffers/VenueMovies.tsx
@@ -1,6 +1,5 @@
import React, { useEffect } from 'react'
import { InView } from 'react-native-intersection-observer'
-import { useTheme } from 'styled-components'
import styled from 'styled-components/native'
import { MoviesScreeningCalendar } from 'features/offer/components/MoviesScreeningCalendar/MoviesScreeningCalendar'
@@ -8,13 +7,12 @@ import { useOfferCTA } from 'features/offer/components/OfferContent/OfferCTAProv
import type { VenueOffers } from 'features/venue/types'
import { Anchor } from 'ui/components/anchor/Anchor'
import { useScrollToAnchor } from 'ui/components/anchor/AnchorContext'
-import { Spacer, TypoDS, getSpacing } from 'ui/theme'
+import { TypoDS, getSpacing } from 'ui/theme'
import { getHeadingAttrs } from 'ui/theme/typographyAttrs/getHeadingAttrs'
const cinemaCTAButtonName = 'Accéder aux séances'
export const VenueMovies: React.FC<{ venueOffers: VenueOffers }> = ({ venueOffers }) => {
- const { isDesktopViewport } = useTheme()
const { setButton, showButton } = useOfferCTA()
const scrollToAnchor = useScrollToAnchor()
@@ -29,8 +27,7 @@ export const VenueMovies: React.FC<{ venueOffers: VenueOffers }> = ({ venueOffer
}, [scrollToAnchor, setButton])
return (
-
-
+
{
@@ -39,12 +36,17 @@ export const VenueMovies: React.FC<{ venueOffers: VenueOffers }> = ({ venueOffer
{'Les films à l’affiche'}
-
+
-
+
)
}
+const Container = styled.View(({ theme }) => ({
+ paddingTop: theme.isDesktopViewport ? getSpacing(10) : getSpacing(6),
+ gap: theme.isDesktopViewport ? getSpacing(10) : getSpacing(6),
+}))
+
const MoviesTitle = styled(TypoDS.Title3).attrs(getHeadingAttrs(2))({
marginLeft: getSpacing(6),
})
diff --git a/src/features/venue/components/VenueOffers/VenueOffers.native.test.tsx b/src/features/venue/components/VenueOffers/VenueOffers.native.test.tsx
index b64a59d4ca6..46218dd6cbc 100644
--- a/src/features/venue/components/VenueOffers/VenueOffers.native.test.tsx
+++ b/src/features/venue/components/VenueOffers/VenueOffers.native.test.tsx
@@ -20,14 +20,13 @@ import {
} from 'features/venue/fixtures/venueOffersResponseSnap'
import type { VenueOffersArtists, VenueOffers as VenueOffersType } from 'features/venue/types'
import { analytics } from 'libs/analytics'
-import * as useFeatureFlag from 'libs/firebase/firestore/featureFlags/useFeatureFlag'
+import { setFeatureFlags } from 'libs/firebase/firestore/featureFlags/__tests__/setFeatureFlags'
+import { RemoteStoreFeatureFlags } from 'libs/firebase/firestore/types'
import { LocationMode } from 'libs/location/types'
import { reactQueryProviderHOC } from 'tests/reactQueryProviderHOC'
import { render, screen, userEvent } from 'tests/utils'
import { AnchorProvider } from 'ui/components/anchor/AnchorContext'
-const mockFeatureFlag = jest.spyOn(useFeatureFlag, 'useFeatureFlag').mockReturnValue(false)
-
const playlists = gtlPlaylistAlgoliaSnapshot
const mockVenue = venueDataTest
const venueId = venueDataTest.id
@@ -108,6 +107,10 @@ const user = userEvent.setup()
jest.useFakeTimers()
describe('', () => {
+ beforeEach(() => {
+ setFeatureFlags()
+ })
+
it('should display skeleton if offers are fetching', () => {
jest.spyOn(useVenueOffers, 'useVenueOffers').mockReturnValueOnce({
isLoading: true,
@@ -220,8 +223,8 @@ describe('', () => {
})
describe('Cinema venue', () => {
- beforeAll(() => {
- mockFeatureFlag.mockReturnValue(true)
+ beforeEach(() => {
+ setFeatureFlags([RemoteStoreFeatureFlags.WIP_ENABLE_NEW_XP_CINE_FROM_VENUE])
})
it('should display movie screening calendar if at least one offer is a movie screening', async () => {
@@ -236,8 +239,8 @@ describe('', () => {
describe('Artist playlist', () => {
describe('When wipVenueArtistsPlaylist feature flag activated', () => {
- beforeAll(() => {
- mockFeatureFlag.mockReturnValue(true)
+ beforeEach(() => {
+ setFeatureFlags([RemoteStoreFeatureFlags.WIP_VENUE_ARTISTS_PLAYLIST])
})
it('should display artists playlist when venue offers have artists', () => {
@@ -277,10 +280,6 @@ describe('', () => {
})
describe('When wipVenueArtistsPlaylist feature flag deactivated', () => {
- beforeAll(() => {
- mockFeatureFlag.mockReturnValue(false)
- })
-
it('should not display artists playlist when venue offers have artists', () => {
renderVenueOffers({
venue: venueDataTest,
From acf110aca04fbff6b53a541f58cf1a91656389e0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?In=C3=A8s=20Mouandjo=20Lob=C3=A9?=
<158567429+imouandjolobe-pass@users.noreply.github.com>
Date: Wed, 22 Jan 2025 17:42:11 +0100
Subject: [PATCH 09/12] dev review: make more accurate test and revert function
change
---
.../hook/getVenueMovieOffers.native.test.ts | 21 ++++++++++++++-----
.../moviesOffer.builder.test.ts | 7 +++++--
.../moviesOffer.builder.ts | 8 +++----
3 files changed, 24 insertions(+), 12 deletions(-)
diff --git a/src/features/offer/components/MoviesScreeningCalendar/hook/getVenueMovieOffers.native.test.ts b/src/features/offer/components/MoviesScreeningCalendar/hook/getVenueMovieOffers.native.test.ts
index 1aa20e25605..bee51a5d3c5 100644
--- a/src/features/offer/components/MoviesScreeningCalendar/hook/getVenueMovieOffers.native.test.ts
+++ b/src/features/offer/components/MoviesScreeningCalendar/hook/getVenueMovieOffers.native.test.ts
@@ -12,6 +12,9 @@ const BEFORE_SELECTED_DATE = '2023-05-07T00:00:00.000Z'
const SELECTED_DATE = '2023-05-15T00:00:00.000Z'
const AFTER_SELECTED_DATE = '2023-05-20T00:00:00.000Z'
const AT_LEAST_15_DAYS_AFTER_SELECTED_DATE = '2023-06-20T00:00:00.000Z'
+const SELECTED_DATE_PLUS_1_SECOND = '2023-05-15T00:00:01.000Z'
+const SELECTED_DATE_PLUS_2_SECOND = '2023-05-15T00:00:02.000Z'
+const SELECTED_DATE_PLUS_3_SECOND = '2023-05-15T00:00:03.000Z'
describe('useVenueMovieOffers', () => {
beforeEach(() => {
@@ -24,17 +27,23 @@ describe('useVenueMovieOffers', () => {
mockBuilder.offerResponseV2({
id: 1,
last30DaysBookings: 400,
- stocks: [mockBuilder.offerStockResponse({ beginningDatetime: SELECTED_DATE })],
+ stocks: [
+ mockBuilder.offerStockResponse({ beginningDatetime: SELECTED_DATE_PLUS_1_SECOND }),
+ ],
}),
mockBuilder.offerResponseV2({
id: 2,
last30DaysBookings: 200,
- stocks: [mockBuilder.offerStockResponse({ beginningDatetime: SELECTED_DATE })],
+ stocks: [
+ mockBuilder.offerStockResponse({ beginningDatetime: SELECTED_DATE_PLUS_2_SECOND }),
+ ],
}),
mockBuilder.offerResponseV2({
id: 3,
last30DaysBookings: 300,
- stocks: [mockBuilder.offerStockResponse({ beginningDatetime: SELECTED_DATE })],
+ stocks: [
+ mockBuilder.offerStockResponse({ beginningDatetime: SELECTED_DATE_PLUS_3_SECOND }),
+ ],
}),
],
}
@@ -58,7 +67,9 @@ describe('useVenueMovieOffers', () => {
offers: [
mockBuilder.offerResponseV2({
id: 1,
- stocks: [mockBuilder.offerStockResponse({ beginningDatetime: SELECTED_DATE })],
+ stocks: [
+ mockBuilder.offerStockResponse({ beginningDatetime: SELECTED_DATE_PLUS_1_SECOND }),
+ ],
}),
mockBuilder.offerResponseV2({
id: 2,
@@ -119,7 +130,7 @@ describe('useVenueMovieOffers', () => {
id: 1,
stocks: [
mockBuilder.offerStockResponse({
- beginningDatetime: SELECTED_DATE,
+ beginningDatetime: SELECTED_DATE_PLUS_1_SECOND,
}),
],
}),
diff --git a/src/features/offer/components/MoviesScreeningCalendar/moviesOffer.builder.test.ts b/src/features/offer/components/MoviesScreeningCalendar/moviesOffer.builder.test.ts
index a9c20a2f68c..6079bf0573b 100644
--- a/src/features/offer/components/MoviesScreeningCalendar/moviesOffer.builder.test.ts
+++ b/src/features/offer/components/MoviesScreeningCalendar/moviesOffer.builder.test.ts
@@ -1,4 +1,4 @@
-import { addDays } from 'date-fns'
+import { addDays, addSeconds } from 'date-fns'
import mockdate from 'mockdate'
import { mockBuilder } from 'tests/mockBuilder'
@@ -36,8 +36,11 @@ describe('moviesOfferBuilder', () => {
describe('withoutMoviesOnDay', () => {
it('should not return movies of the selected day', () => {
+ const selectedDatePlus1Second = addSeconds(selectedDate, 1)
const offer1 = mockBuilder.offerResponseV2({
- stocks: [mockBuilder.offerStockResponse({ beginningDatetime: selectedDate.toString() })],
+ stocks: [
+ mockBuilder.offerStockResponse({ beginningDatetime: selectedDatePlus1Second.toString() }),
+ ],
})
const offer2 = mockBuilder.offerResponseV2({
stocks: [
diff --git a/src/features/offer/components/MoviesScreeningCalendar/moviesOffer.builder.ts b/src/features/offer/components/MoviesScreeningCalendar/moviesOffer.builder.ts
index 6c5903c95e9..28fbc1fafbb 100644
--- a/src/features/offer/components/MoviesScreeningCalendar/moviesOffer.builder.ts
+++ b/src/features/offer/components/MoviesScreeningCalendar/moviesOffer.builder.ts
@@ -27,12 +27,10 @@ export const moviesOfferBuilder = (offersWithStocks: OfferResponseV2[] = []) =>
return true
}
const isSameDate = isSameDay(new Date(stock.beginningDatetime), selectedDate)
- if (
- isSameDate ||
- (isSameDate && isAfter(new Date(stock.beginningDatetime), selectedDate))
- ) {
- return true
+ if (isSameDate) {
+ return isAfter(new Date(stock.beginningDatetime), selectedDate)
}
+
return false
})
)
From 81664c675affe8c6497c7556a6457e721c2e819d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?In=C3=A8s=20Mouandjo=20Lob=C3=A9?=
<158567429+imouandjolobe-pass@users.noreply.github.com>
Date: Wed, 22 Jan 2025 22:56:55 +0100
Subject: [PATCH 10/12] test: add more coverage
---
.../MovieOfferTile.native.test.tsx | 257 ++++++++++++++++++
1 file changed, 257 insertions(+)
create mode 100644 src/features/offer/components/MoviesScreeningCalendar/MovieOfferTile.native.test.tsx
diff --git a/src/features/offer/components/MoviesScreeningCalendar/MovieOfferTile.native.test.tsx b/src/features/offer/components/MoviesScreeningCalendar/MovieOfferTile.native.test.tsx
new file mode 100644
index 00000000000..1a9024c5e74
--- /dev/null
+++ b/src/features/offer/components/MoviesScreeningCalendar/MovieOfferTile.native.test.tsx
@@ -0,0 +1,257 @@
+import { addDays } from 'date-fns'
+import mockdate from 'mockdate'
+import React, { createRef } from 'react'
+import { ScrollView } from 'react-native'
+
+import {
+ CategoryIdEnum,
+ NativeCategoryIdEnumv2,
+ SearchGroupNameEnumv2,
+ SubcategoryIdEnum,
+} from 'api/gen'
+import { MovieCalendarProvider } from 'features/offer/components/MoviesScreeningCalendar/MovieCalendarContext'
+import * as MovieCalendarContext from 'features/offer/components/MoviesScreeningCalendar/MovieCalendarContext'
+import { MovieOfferTile } from 'features/offer/components/MoviesScreeningCalendar/MovieOfferTile'
+import { NEXT_SCREENING_WORDING } from 'features/offer/components/MoviesScreeningCalendar/NextScreeningButton'
+import { MovieOffer } from 'features/offer/components/MoviesScreeningCalendar/types'
+import { VenueOffers } from 'features/venue/types'
+import { setFeatureFlags } from 'libs/firebase/firestore/featureFlags/__tests__/setFeatureFlags'
+import { getDates } from 'shared/date/getDates'
+import { mockBuilder } from 'tests/mockBuilder'
+import { reactQueryProviderHOC } from 'tests/reactQueryProviderHOC'
+import { render, screen, userEvent } from 'tests/utils'
+import { AnchorProvider } from 'ui/components/anchor/AnchorContext'
+
+jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter')
+jest.mock('libs/firebase/analytics/analytics')
+jest.mock('react-native/Libraries/Animated/createAnimatedComponent', () => {
+ return function createAnimatedComponent(Component: unknown) {
+ return Component
+ }
+})
+
+const MOCK_TIMESTAMP = '2024-05-08T12:50:00Z'
+const MOCK_DATE = new Date(MOCK_TIMESTAMP)
+
+const VENUE_OFFERS_HIT = {
+ _geoloc: { lat: 47.8898, lng: -2.83593 },
+ objectID: '1234',
+ offer: {
+ dates: [MOCK_DATE.getTime(), MOCK_DATE.getTime()],
+ isDigital: false,
+ isDuo: true,
+ name: 'Harry potter',
+ prices: [7],
+ subcategoryId: SubcategoryIdEnum.SEANCE_CINE,
+ thumbUrl:
+ 'https://storage.googleapis.com/passculture-metier-ehp-testing-assets-fine-grained/thumbs/mediations/AQBA',
+ },
+ venue: {},
+}
+
+const VENUE_OFFERS_MOCK = { hits: [VENUE_OFFERS_HIT], nbHits: 1 }
+
+const MOCK_MOVIE_OFFER = {
+ isUpcoming: false,
+ nextDate: new Date('2025-02-07T20:30:10Z'),
+ offer: mockBuilder.offerResponseV2({
+ id: 1,
+ stocks: [
+ mockBuilder.offerStockResponse({
+ beginningDatetime: MOCK_TIMESTAMP,
+ }),
+ ],
+ }),
+}
+
+const mockSelectedDate = new Date('2024-05-02')
+const mockGoToDate = jest.fn()
+const mockDisplayCalendar = jest.fn()
+
+const mockUseOfferCTAButton = jest.fn()
+const mockOnPressOfferCTA = jest.fn()
+
+const mockUseSubcategoriesMapping = jest.fn()
+jest.mock('libs/subcategories/mappings', () => ({
+ useSubcategoriesMapping: jest.fn(() => mockUseSubcategoriesMapping()),
+}))
+
+const mockUseSearchGroupLabel = jest.fn()
+jest.mock('libs/subcategories/useSearchGroupLabel', () => ({
+ useSearchGroupLabel: jest.fn(() => mockUseSearchGroupLabel()),
+}))
+
+jest.mock('features/offer/components/OfferCTAButton/useOfferCTAButton', () => ({
+ useOfferCTAButton: jest.fn(() => mockUseOfferCTAButton()),
+}))
+
+describe('MovieOfferTile', () => {
+ beforeEach(() => {
+ setFeatureFlags()
+ jest.spyOn(MovieCalendarContext, 'useMovieCalendar').mockReturnValue({
+ selectedDate: mockSelectedDate,
+ goToDate: mockGoToDate,
+ displayCalendar: mockDisplayCalendar,
+ dates: [],
+ disableDates: jest.fn(),
+ displayDates: jest.fn(),
+ })
+
+ mockUseSubcategoriesMapping.mockReturnValue({
+ [SubcategoryIdEnum.SEANCE_CINE]: {
+ isEvent: false,
+ categoryId: CategoryIdEnum.CINEMA,
+ nativeCategoryId: NativeCategoryIdEnumv2.SEANCES_DE_CINEMA,
+ },
+ })
+
+ mockUseSearchGroupLabel.mockReturnValue(SearchGroupNameEnumv2.CINEMA)
+
+ mockUseOfferCTAButton.mockReturnValue({
+ ctaWordingAndAction: {
+ onPress: jest.fn(),
+ bottomBannerText: 'CTA',
+ externalNav: {
+ url: '',
+ },
+ isDisabled: false,
+ wording: 'CTA',
+ navigateTo: {
+ screen: 'Offer',
+ },
+ },
+ showOfferModal: jest.fn(),
+ openModalOnNavigation: false,
+ onPress: mockOnPressOfferCTA,
+ CTAOfferModal: null,
+ movieScreeningUserData: {},
+ })
+ })
+
+ jest.useFakeTimers()
+ const user = userEvent.setup()
+
+ describe('with screening on selected date', () => {
+ const ID = '4321'
+ const movieOffer = { ...MOCK_MOVIE_OFFER, offer: { ...MOCK_MOVIE_OFFER.offer, id: +ID } }
+ const venueOffersHit = { ...VENUE_OFFERS_HIT, objectID: ID }
+ const venueOffers = { ...VENUE_OFFERS_MOCK, hits: [...VENUE_OFFERS_MOCK.hits, venueOffersHit] }
+
+ it('should render offer component', async () => {
+ renderMovieOfferTile({ movieOffer, venueOffers })
+
+ expect(await screen.findByText(VENUE_OFFERS_HIT.offer.name)).toBeOnTheScreen()
+ })
+ })
+
+ describe('without screening on selected date', () => {
+ const ID = '4321'
+ const venueOffersHit = { ...VENUE_OFFERS_HIT, objectID: ID }
+ const venueOffers = { ...VENUE_OFFERS_MOCK, hits: [...VENUE_OFFERS_MOCK.hits, venueOffersHit] }
+
+ it('should not render offer component', () => {
+ renderMovieOfferTile({ movieOffer: MOCK_MOVIE_OFFER, venueOffers })
+
+ expect(screen.queryByText(VENUE_OFFERS_HIT.offer.name)).not.toBeOnTheScreen()
+ })
+ })
+
+ describe('with next screening date', () => {
+ beforeEach(() => mockdate.set(MOCK_DATE))
+
+ const TODAY_PLUS_20_DAYS = addDays(MOCK_DATE, 20)
+ const TODAY_PLUS_10_DAYS = addDays(MOCK_DATE, 10)
+
+ it('should render next screening button', async () => {
+ renderMovieOfferTile({
+ movieOffer: MOCK_MOVIE_OFFER,
+ venueOffers: VENUE_OFFERS_MOCK,
+ nextScreeningDate: TODAY_PLUS_20_DAYS,
+ })
+
+ expect(await screen.findByText(NEXT_SCREENING_WORDING)).toBeOnTheScreen()
+ })
+
+ it('should open booking modal on press when nextScreeningDate is at least 15 days after today', async () => {
+ renderMovieOfferTile({
+ movieOffer: MOCK_MOVIE_OFFER,
+ venueOffers: VENUE_OFFERS_MOCK,
+ nextScreeningDate: TODAY_PLUS_20_DAYS,
+ })
+
+ const nextScreeningButton = await screen.findByText(NEXT_SCREENING_WORDING)
+
+ await user.press(nextScreeningButton)
+
+ expect(mockOnPressOfferCTA).toHaveBeenCalledWith()
+ })
+
+ it('should trigger calendar scroll on press when nextScreeningDate is within the next 15 days', async () => {
+ renderMovieOfferTile({
+ movieOffer: MOCK_MOVIE_OFFER,
+ venueOffers: VENUE_OFFERS_MOCK,
+ nextScreeningDate: TODAY_PLUS_10_DAYS,
+ })
+
+ const nextScreeningButton = await screen.findByText(NEXT_SCREENING_WORDING)
+
+ await user.press(nextScreeningButton)
+
+ expect(mockGoToDate).toHaveBeenCalledWith(TODAY_PLUS_10_DAYS)
+ })
+ })
+
+ describe('without next screening date', () => {
+ it('should render event card list component', async () => {
+ const ID = '4321'
+ const movieOffer = { ...MOCK_MOVIE_OFFER, offer: { ...MOCK_MOVIE_OFFER.offer, id: +ID } }
+ const venueOffersHit = { ...VENUE_OFFERS_HIT, objectID: ID }
+ const venueOffers = {
+ ...VENUE_OFFERS_MOCK,
+ hits: [...VENUE_OFFERS_MOCK.hits, venueOffersHit],
+ }
+
+ renderMovieOfferTile({
+ movieOffer,
+ venueOffers,
+ isDesktopViewport: true,
+ })
+
+ await screen.findByText(VENUE_OFFERS_HIT.offer.name)
+
+ expect(await screen.findByTestId('desktop-event-card-list')).toBeOnTheScreen()
+ })
+ })
+})
+
+const next15Dates = getDates(new Date(), 15)
+
+const renderMovieOfferTile = ({
+ movieOffer,
+ venueOffers,
+ nextScreeningDate,
+ isDesktopViewport = false,
+}: {
+ movieOffer: MovieOffer
+ venueOffers: VenueOffers
+ nextScreeningDate?: Date
+ isDesktopViewport?: boolean
+}) => {
+ render(
+ reactQueryProviderHOC(
+ ()} handleCheckScrollY={() => 0}>
+
+
+
+
+ ),
+ {
+ theme: { isDesktopViewport },
+ }
+ )
+}
From cbef60bad8b700c1820eb75f9d60ddae1f05927c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?In=C3=A8s=20Mouandjo=20Lob=C3=A9?=
<158567429+imouandjolobe-pass@users.noreply.github.com>
Date: Wed, 22 Jan 2025 22:57:26 +0100
Subject: [PATCH 11/12] refactor: remove some useMemo
---
.../useGetVenueByDay/useGetVenuesByDay.ts | 50 +++++++------------
1 file changed, 18 insertions(+), 32 deletions(-)
diff --git a/src/features/offer/helpers/useGetVenueByDay/useGetVenuesByDay.ts b/src/features/offer/helpers/useGetVenueByDay/useGetVenuesByDay.ts
index 043f0b368ef..54cc102e5db 100644
--- a/src/features/offer/helpers/useGetVenueByDay/useGetVenuesByDay.ts
+++ b/src/features/offer/helpers/useGetVenueByDay/useGetVenuesByDay.ts
@@ -1,5 +1,4 @@
import { isToday } from 'date-fns'
-import { useMemo } from 'react'
import { OfferResponseV2 } from 'api/gen'
import { moviesOfferBuilder } from 'features/offer/components/MoviesScreeningCalendar/moviesOffer.builder'
@@ -8,40 +7,27 @@ import { useUserLocation } from 'features/offer/helpers/useUserLocation/useUserL
export const useGetVenuesByDay = (date: Date, offers: OfferResponseV2[]) => {
const location = useUserLocation()
- const dayOffers = useMemo(
- () =>
- moviesOfferBuilder(offers)
- .sortedByDistance(location)
- .withScreeningsOnDay(date)
- .withoutScreeningsAfterNbDays(15)
- .build(),
- [date, location, offers]
- )
+ const dayOffers = moviesOfferBuilder(offers)
+ .sortedByDistance(location)
+ .withScreeningsOnDay(date)
+ .build()
- const nextOffers = useMemo(() => {
- return moviesOfferBuilder(offers)
- .withoutScreeningsAfterNbDays(15)
- .withoutScreeningsOnDay(date)
- .withNextScreeningFromDate(date)
- .sortedByDistance(location)
- .build()
- }, [date, location, offers])
-
- const after15DaysOffers = useMemo(
- () =>
- moviesOfferBuilder(offers).sortedByDistance(location).withScreeningsAfterNbDays(15).build(),
- [location, offers]
- )
+ const nextOffers = moviesOfferBuilder(offers)
+ .withoutScreeningsAfterNbDays(15)
+ .withoutScreeningsOnDay(date)
+ .withNextScreeningFromDate(date)
+ .sortedByDistance(location)
+ .build()
- const movieOffers = useMemo(
- () => [...dayOffers, ...nextOffers, ...after15DaysOffers],
- [dayOffers, nextOffers, after15DaysOffers]
- )
+ const after15DaysOffers = moviesOfferBuilder(offers)
+ .sortedByDistance(location)
+ .withScreeningsAfterNbDays(15)
+ .build()
- const hasStocksOnlyAfter15Days = useMemo(
- () => !dayOffers.length && !nextOffers.length && after15DaysOffers.length > 0,
- [after15DaysOffers.length, dayOffers.length, nextOffers.length]
- )
+ const movieOffers = [...dayOffers, ...nextOffers, ...after15DaysOffers]
+
+ const hasStocksOnlyAfter15Days =
+ !dayOffers.length && !nextOffers.length && after15DaysOffers.length > 0
return {
movieOffers,
From 889f5af574e01137f7e6b92adc816f22c93957c5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?In=C3=A8s=20Mouandjo=20Lob=C3=A9?=
<158567429+imouandjolobe-pass@users.noreply.github.com>
Date: Thu, 23 Jan 2025 00:30:16 +0100
Subject: [PATCH 12/12] clean: small cleaning in MovieOfferTile test
---
.../MovieOfferTile.native.test.tsx | 115 +++++++-----------
1 file changed, 45 insertions(+), 70 deletions(-)
diff --git a/src/features/offer/components/MoviesScreeningCalendar/MovieOfferTile.native.test.tsx b/src/features/offer/components/MoviesScreeningCalendar/MovieOfferTile.native.test.tsx
index 1a9024c5e74..515218ffb99 100644
--- a/src/features/offer/components/MoviesScreeningCalendar/MovieOfferTile.native.test.tsx
+++ b/src/features/offer/components/MoviesScreeningCalendar/MovieOfferTile.native.test.tsx
@@ -65,72 +65,69 @@ const MOCK_MOVIE_OFFER = {
}
const mockSelectedDate = new Date('2024-05-02')
-const mockGoToDate = jest.fn()
const mockDisplayCalendar = jest.fn()
-
-const mockUseOfferCTAButton = jest.fn()
-const mockOnPressOfferCTA = jest.fn()
+const mockGoToDate = jest.fn()
+jest.spyOn(MovieCalendarContext, 'useMovieCalendar').mockReturnValue({
+ selectedDate: mockSelectedDate,
+ goToDate: mockGoToDate,
+ displayCalendar: mockDisplayCalendar,
+ dates: [],
+ disableDates: jest.fn(),
+ displayDates: jest.fn(),
+})
const mockUseSubcategoriesMapping = jest.fn()
+mockUseSubcategoriesMapping.mockReturnValue({
+ [SubcategoryIdEnum.SEANCE_CINE]: {
+ isEvent: false,
+ categoryId: CategoryIdEnum.CINEMA,
+ nativeCategoryId: NativeCategoryIdEnumv2.SEANCES_DE_CINEMA,
+ },
+})
jest.mock('libs/subcategories/mappings', () => ({
- useSubcategoriesMapping: jest.fn(() => mockUseSubcategoriesMapping()),
+ useSubcategoriesMapping: () => mockUseSubcategoriesMapping(),
}))
const mockUseSearchGroupLabel = jest.fn()
+mockUseSearchGroupLabel.mockReturnValue(SearchGroupNameEnumv2.CINEMA)
jest.mock('libs/subcategories/useSearchGroupLabel', () => ({
- useSearchGroupLabel: jest.fn(() => mockUseSearchGroupLabel()),
+ useSearchGroupLabel: () => mockUseSearchGroupLabel(),
}))
+const mockOnPressOfferCTA = jest.fn()
+const mockUseOfferCTAButton = jest.fn()
+mockUseOfferCTAButton.mockReturnValue({
+ ctaWordingAndAction: {
+ onPress: jest.fn(),
+ bottomBannerText: 'CTA',
+ externalNav: {
+ url: '',
+ },
+ isDisabled: false,
+ wording: 'CTA',
+ navigateTo: {
+ screen: 'Offer',
+ },
+ },
+ showOfferModal: jest.fn(),
+ openModalOnNavigation: false,
+ onPress: mockOnPressOfferCTA,
+ CTAOfferModal: null,
+ movieScreeningUserData: {},
+})
jest.mock('features/offer/components/OfferCTAButton/useOfferCTAButton', () => ({
- useOfferCTAButton: jest.fn(() => mockUseOfferCTAButton()),
+ useOfferCTAButton: () => mockUseOfferCTAButton(),
}))
+jest.useFakeTimers()
+const user = userEvent.setup()
+
describe('MovieOfferTile', () => {
beforeEach(() => {
+ jest.clearAllMocks()
setFeatureFlags()
- jest.spyOn(MovieCalendarContext, 'useMovieCalendar').mockReturnValue({
- selectedDate: mockSelectedDate,
- goToDate: mockGoToDate,
- displayCalendar: mockDisplayCalendar,
- dates: [],
- disableDates: jest.fn(),
- displayDates: jest.fn(),
- })
-
- mockUseSubcategoriesMapping.mockReturnValue({
- [SubcategoryIdEnum.SEANCE_CINE]: {
- isEvent: false,
- categoryId: CategoryIdEnum.CINEMA,
- nativeCategoryId: NativeCategoryIdEnumv2.SEANCES_DE_CINEMA,
- },
- })
-
- mockUseSearchGroupLabel.mockReturnValue(SearchGroupNameEnumv2.CINEMA)
-
- mockUseOfferCTAButton.mockReturnValue({
- ctaWordingAndAction: {
- onPress: jest.fn(),
- bottomBannerText: 'CTA',
- externalNav: {
- url: '',
- },
- isDisabled: false,
- wording: 'CTA',
- navigateTo: {
- screen: 'Offer',
- },
- },
- showOfferModal: jest.fn(),
- openModalOnNavigation: false,
- onPress: mockOnPressOfferCTA,
- CTAOfferModal: null,
- movieScreeningUserData: {},
- })
})
- jest.useFakeTimers()
- const user = userEvent.setup()
-
describe('with screening on selected date', () => {
const ID = '4321'
const movieOffer = { ...MOCK_MOVIE_OFFER, offer: { ...MOCK_MOVIE_OFFER.offer, id: +ID } }
@@ -200,28 +197,6 @@ describe('MovieOfferTile', () => {
expect(mockGoToDate).toHaveBeenCalledWith(TODAY_PLUS_10_DAYS)
})
})
-
- describe('without next screening date', () => {
- it('should render event card list component', async () => {
- const ID = '4321'
- const movieOffer = { ...MOCK_MOVIE_OFFER, offer: { ...MOCK_MOVIE_OFFER.offer, id: +ID } }
- const venueOffersHit = { ...VENUE_OFFERS_HIT, objectID: ID }
- const venueOffers = {
- ...VENUE_OFFERS_MOCK,
- hits: [...VENUE_OFFERS_MOCK.hits, venueOffersHit],
- }
-
- renderMovieOfferTile({
- movieOffer,
- venueOffers,
- isDesktopViewport: true,
- })
-
- await screen.findByText(VENUE_OFFERS_HIT.offer.name)
-
- expect(await screen.findByTestId('desktop-event-card-list')).toBeOnTheScreen()
- })
- })
})
const next15Dates = getDates(new Date(), 15)