diff --git a/src/app/Scenes/Activity/components/PartnerOfferCreatedNotification.tsx b/src/app/Scenes/Activity/components/PartnerOfferCreatedNotification.tsx index a6fde83f6ad..2dc5fd06ca7 100644 --- a/src/app/Scenes/Activity/components/PartnerOfferCreatedNotification.tsx +++ b/src/app/Scenes/Activity/components/PartnerOfferCreatedNotification.tsx @@ -41,9 +41,9 @@ export const PartnerOfferCreatedNotification: React.FC - {subtitle} + {!!subtitle && {subtitle}} diff --git a/src/app/Scenes/Activity/components/__tests__/PartnerOfferCreatedNotification.tests.tsx b/src/app/Scenes/Activity/components/__tests__/PartnerOfferCreatedNotification.tests.tsx index 57b1d722a8d..5d3dcd10c60 100644 --- a/src/app/Scenes/Activity/components/__tests__/PartnerOfferCreatedNotification.tests.tsx +++ b/src/app/Scenes/Activity/components/__tests__/PartnerOfferCreatedNotification.tests.tsx @@ -60,7 +60,7 @@ describe("PartnerOfferCreatedNotification", () => { expect(screen.getByText("$405,000")).toBeOnTheScreen() expect(screen.getByText(/List price:\s*\$450,000\s*/)).toBeOnTheScreen() expect(screen.getByText('"This is a note from the gallery"')).toBeOnTheScreen() - expect(screen.getByText("Review the offer before it expires")).toBeOnTheScreen() + expect(screen.queryByText("Review the offer before it expires")).not.toBeOnTheScreen() expect(screen.queryByText("Review the offer on your saved artwork")).not.toBeOnTheScreen() expect(screen.queryByText("Manage Saves")).not.toBeOnTheScreen() }) diff --git a/src/app/Scenes/Inbox/Components/Conversations/__tests__/ConversationCTA.tests.tsx b/src/app/Scenes/Inbox/Components/Conversations/__tests__/ConversationCTA.tests.tsx index fd7edd92117..06737d57075 100644 --- a/src/app/Scenes/Inbox/Components/Conversations/__tests__/ConversationCTA.tests.tsx +++ b/src/app/Scenes/Inbox/Components/Conversations/__tests__/ConversationCTA.tests.tsx @@ -95,52 +95,70 @@ describe("conversation about an artwork with inquiry checkout enabled", () => { expect(screen.UNSAFE_queryAllByType(OpenInquiryModalButton)).toHaveLength(1) }) - describe("with an active partner offer", () => { - const futureISO = () => new Date(Date.now() + 60 * 60 * 1000).toISOString() - - const partnerOfferResolvers = { - Conversation: () => ({ - items: [{ item: { __typename: "Artwork" }, liveArtwork: { __typename: "Artwork" } }], - activeOrders: { edges: [] }, - }), - // Ensures both `items.item` and `liveArtwork` resolve to the same artwork id - // that the offer below references. - Artwork: () => ({ internalID: "123", href: "/artwork/foo", isOfferableFromInquiry: true }), - Me: () => ({ - partnerOffersConnection: { - edges: [ - { - node: { - internalID: "partner-offer-id", - artworkId: "123", - endAt: futureISO(), - isAvailable: true, - priceWithDiscount: { display: "US$450" }, + // The `partnerOffersConnection` query fetches both `BULK` (sent via the + // partner dashboard's send-offer tool) and `PERSONALIZED` offer types. + // Eigen can't distinguish between the two once fetched, so both should + // behave identically here. + describe.each([["a personalized partner offer"], ["a bulk offer (send offer)"]])( + "with %s", + () => { + const futureISO = () => new Date(Date.now() + 60 * 60 * 1000).toISOString() + const pastISO = () => new Date(Date.now() - 60 * 60 * 1000).toISOString() + + const buildPartnerOfferResolvers = (offerOverrides: Record = {}) => ({ + Conversation: () => ({ + items: [{ item: { __typename: "Artwork" }, liveArtwork: { __typename: "Artwork" } }], + activeOrders: { edges: [] }, + }), + // Ensures both `items.item` and `liveArtwork` resolve to the same artwork id + // that the offer below references. + Artwork: () => ({ internalID: "123", href: "/artwork/foo", isOfferableFromInquiry: true }), + Me: () => ({ + partnerOffersConnection: { + edges: [ + { + node: { + internalID: "partner-offer-id", + artworkId: "123", + endAt: futureISO(), + isAvailable: true, + priceWithDiscount: { display: "US$450" }, + ...offerOverrides, + }, }, - }, - ], - }, - }), - } + ], + }, + }), + }) - it("replaces the inquiry buttons with the offer banner when the flag is on", () => { - __globalStoreTestUtils__?.injectFeatureFlags({ AREnableConversationPartnerOffers: true }) + it("replaces the inquiry buttons with the offer banner when the flag is on", () => { + __globalStoreTestUtils__?.injectFeatureFlags({ AREnableConversationPartnerOffers: true }) - renderWithRelay(partnerOfferResolvers) + renderWithRelay(buildPartnerOfferResolvers()) - // The dedicated offer banner replaces the inquiry transaction buttons. - expect(screen.UNSAFE_queryAllByType(OpenInquiryModalButton)).toHaveLength(0) - expect(screen.UNSAFE_queryAllByType(ConversationPartnerOfferCTA)).toHaveLength(1) - }) + // The dedicated offer banner replaces the inquiry transaction buttons. + expect(screen.UNSAFE_queryAllByType(OpenInquiryModalButton)).toHaveLength(0) + expect(screen.UNSAFE_queryAllByType(ConversationPartnerOfferCTA)).toHaveLength(1) + }) - it("still renders the inquiry buttons when the flag is off", () => { - __globalStoreTestUtils__?.injectFeatureFlags({ AREnableConversationPartnerOffers: false }) + it("still renders the inquiry buttons when the flag is off", () => { + __globalStoreTestUtils__?.injectFeatureFlags({ AREnableConversationPartnerOffers: false }) - renderWithRelay(partnerOfferResolvers) + renderWithRelay(buildPartnerOfferResolvers()) - expect(screen.UNSAFE_queryAllByType(OpenInquiryModalButton)).toHaveLength(1) - }) - }) + expect(screen.UNSAFE_queryAllByType(OpenInquiryModalButton)).toHaveLength(1) + }) + + it("falls back to the inquiry buttons once the offer expires", () => { + __globalStoreTestUtils__?.injectFeatureFlags({ AREnableConversationPartnerOffers: true }) + + renderWithRelay(buildPartnerOfferResolvers({ endAt: pastISO() })) + + expect(screen.UNSAFE_queryAllByType(ConversationPartnerOfferCTA)).toHaveLength(0) + expect(screen.UNSAFE_queryAllByType(OpenInquiryModalButton)).toHaveLength(1) + }) + } + ) it("renders the payment failed message if the payment failed", () => { renderWithOrders({ lastTransactionFailed: true, mode: "OFFER" }) diff --git a/src/app/Scenes/Inbox/Components/Conversations/__tests__/ConversationPartnerOfferCTA.tests.tsx b/src/app/Scenes/Inbox/Components/Conversations/__tests__/ConversationPartnerOfferCTA.tests.tsx index f97ff1197ae..e79a69c399a 100644 --- a/src/app/Scenes/Inbox/Components/Conversations/__tests__/ConversationPartnerOfferCTA.tests.tsx +++ b/src/app/Scenes/Inbox/Components/Conversations/__tests__/ConversationPartnerOfferCTA.tests.tsx @@ -65,20 +65,37 @@ describe("ConversationPartnerOfferCTA", () => { __globalStoreTestUtils__?.injectFeatureFlags({ AREnableConversationPartnerOffers: true }) }) - it("renders the offer banner, tracks it, and navigates to the artwork with the partner offer id", () => { - renderWithRelay(offerResolvers()) - - expect(screen.getByText("Offer received for $450")).toBeTruthy() - - expect(mockTrackEvent).toHaveBeenCalledWith({ - action: "partnerOfferInConversationViewed", - context_owner_id: "conversation-id", - context_owner_type: "conversation", - }) - - fireEvent.press(screen.getByTestId("partnerOfferActionLink")) - expect(navigate).toHaveBeenCalledWith("/artwork/some-artwork?partner_offer_id=partner-offer-id") - }) + // The `partnerOffersConnection` query fetches both `BULK` (sent via the + // partner dashboard's send-offer tool) and `PERSONALIZED` offer types. + // Eigen can't distinguish between the two once fetched, so the banner + // should render identically for both. + describe.each([["a personalized partner offer"], ["a bulk offer (send offer)"]])( + "with %s", + () => { + it("renders the offer banner, tracks it, and navigates to the artwork with the partner offer id", () => { + renderWithRelay(offerResolvers()) + + expect(screen.getByText("Offer received for $450")).toBeTruthy() + + expect(mockTrackEvent).toHaveBeenCalledWith({ + action: "partnerOfferInConversationViewed", + context_owner_id: "conversation-id", + context_owner_type: "conversation", + }) + + fireEvent.press(screen.getByTestId("partnerOfferActionLink")) + expect(navigate).toHaveBeenCalledWith( + "/artwork/some-artwork?partner_offer_id=partner-offer-id" + ) + }) + + it("renders nothing when the offer has expired", () => { + renderWithRelay(offerResolvers({ endAt: pastISO() })) + + expect(screen.queryByTestId("partnerOfferActionLink")).toBeNull() + }) + } + ) it("falls back to a generic title when there is no discounted price", () => { renderWithRelay(offerResolvers({ priceWithDiscount: null })) @@ -103,12 +120,6 @@ describe("ConversationPartnerOfferCTA", () => { expect(screen.queryByTestId("partnerOfferActionLink")).toBeNull() }) - it("renders nothing when the offer has expired", () => { - renderWithRelay(offerResolvers({ endAt: pastISO() })) - - expect(screen.queryByTestId("partnerOfferActionLink")).toBeNull() - }) - it("renders nothing when the offer is unavailable", () => { renderWithRelay(offerResolvers({ isAvailable: false })) diff --git a/src/app/Scenes/Inbox/hooks/usePartnerOffer.ts b/src/app/Scenes/Inbox/hooks/usePartnerOffer.ts index 03fe46d320f..28b0052e71e 100644 --- a/src/app/Scenes/Inbox/hooks/usePartnerOffer.ts +++ b/src/app/Scenes/Inbox/hooks/usePartnerOffer.ts @@ -26,7 +26,7 @@ export const usePartnerOffer = ({ me, artworkId }: UsePartnerOfferProps) => { const fragment = graphql` fragment usePartnerOffer_me on Me { - partnerOffersConnection(first: 100, offerType: [PERSONALIZED]) { + partnerOffersConnection(first: 100, offerType: [BULK, PERSONALIZED]) { edges { node { ...ConversationPartnerOfferCTA_partnerOffers