From 21b0374e5ca72781e8fd7c5c53c59b379c471bab Mon Sep 17 00:00:00 2001 From: LauraPinilla Date: Mon, 25 Mar 2024 11:24:50 -0600 Subject: [PATCH 1/6] OrderInformation --- blocks/subscriptions-block/_index.scss | 40 ++++-- .../components/OfferCard/index.jsx | 39 +++--- .../components/OfferToProductList/index.jsx | 19 ++- .../components/OrderInformation/index.jsx | 88 +++++++++++++ .../components/OrderInformation/index.test.js | 104 +++++++++++++++ .../components/useOrder.jsx | 114 +++++++++++++++++ .../components/usePaymentRedirect.jsx | 2 +- blocks/subscriptions-block/intl.json | 9 ++ blocks/subscriptions-block/themes/news.json | 120 ++++++++++++++++++ locale/en.json | 3 + 10 files changed, 502 insertions(+), 36 deletions(-) create mode 100644 blocks/subscriptions-block/components/OrderInformation/index.jsx create mode 100644 blocks/subscriptions-block/components/OrderInformation/index.test.js create mode 100644 blocks/subscriptions-block/components/useOrder.jsx diff --git a/blocks/subscriptions-block/_index.scss b/blocks/subscriptions-block/_index.scss index 9110d00343..6c263861e8 100644 --- a/blocks/subscriptions-block/_index.scss +++ b/blocks/subscriptions-block/_index.scss @@ -136,24 +136,40 @@ @include scss.block-properties("checkout-payment"); } - &__test { - &-1 { - @include scss.block-components("checkout-payment-test-1"); - @include scss.block-properties("checkout-payment-test-1"); + &__orderCard { + &--productPrice { + @include scss.block-components("checkout-order-card-productPrice"); + @include scss.block-properties("checkout-order-card-productPrice"); } - &-2 { - @include scss.block-components("checkout-payment-test-2"); - @include scss.block-properties("checkout-payment-test-2"); + &__card--features { + &--feature-item { + @include scss.block-components("checkout-order-card--features--item"); + @include scss.block-properties("checkout-order-card--features--item"); + } + @include scss.block-components("checkout-order-card--features"); + @include scss.block-properties("checkout-order-card--features"); } - &-row { - @include scss.block-components("checkout-payment-test-row"); - @include scss.block-properties("checkout-payment-test-row"); + &__summary { + &--dueToday{ + @include scss.block-components("checkout-order-card--summary--dueToday"); + @include scss.block-properties("checkout-order-card--summary--dueToday"); + } + &--details{ + &--item{ + @include scss.block-components("checkout-order-card--summary--details--item"); + @include scss.block-properties("checkout-order-card--summary--details--item"); + } + @include scss.block-components("checkout-order-card--summary--details"); + @include scss.block-properties("checkout-order-card--summary--details"); + } + @include scss.block-components("checkout-order-card--summary"); + @include scss.block-properties("checkout-order-card--summary"); } - @include scss.block-components("checkout-payment-test"); - @include scss.block-properties("checkout-payment-test"); + @include scss.block-components("checkout-order-card"); + @include scss.block-properties("checkout-order-card"); } @include scss.block-components("checkout"); diff --git a/blocks/subscriptions-block/components/OfferCard/index.jsx b/blocks/subscriptions-block/components/OfferCard/index.jsx index 7cd4adb89d..f026511480 100644 --- a/blocks/subscriptions-block/components/OfferCard/index.jsx +++ b/blocks/subscriptions-block/components/OfferCard/index.jsx @@ -2,7 +2,25 @@ import React from "react"; import PropTypes from "@arc-fusion/prop-types"; import { Heading, Button, Stack, Paragraph, Icon } from "@wpmedia/arc-themes-components"; -// TO-DO: Change the Icon to Check +export const FeatureDetails = ({ features, className }) => { + if (features.length) { + return ( +
    + {features.map((feat) => ( +
  • + + +
  • + ))} +
+ ); + } + return null; +}; + const OfferCard = ({ headline, subHeadline, @@ -21,20 +39,7 @@ const OfferCard = ({ ) : null} - - {features.length ? ( -
    - {features.map((feat) => ( -
  • - - -
  • - ))} -
- ) : null} + ); @@ -46,8 +51,8 @@ OfferCard.propTypes = { features: PropTypes.arrayOf( PropTypes.shape({ featureText: PropTypes.string, - }) + }), ), }; -export default OfferCard; \ No newline at end of file +export default OfferCard; diff --git a/blocks/subscriptions-block/components/OfferToProductList/index.jsx b/blocks/subscriptions-block/components/OfferToProductList/index.jsx index 2fc47c5a55..83b72bcc66 100644 --- a/blocks/subscriptions-block/components/OfferToProductList/index.jsx +++ b/blocks/subscriptions-block/components/OfferToProductList/index.jsx @@ -4,6 +4,9 @@ import useSales from "../useSales"; import { Grid } from "@wpmedia/arc-themes-components"; import OfferCard from "../OfferCard"; +export const ARCXP_CART = 'ArcXP_cart'; +export const ARCXP_CAMPAIGN = 'ArcXP_campaignName'; + const OfferToProductList = ({ offer, isLoggedIn, checkoutURL, loginURL, className }) => { const { Sales } = useSales(); @@ -21,7 +24,7 @@ const OfferToProductList = ({ offer, isLoggedIn, checkoutURL, loginURL, classNam products[productIdx].attributes.length !== 0 ? products[productIdx].attributes.map((feature) => ({ featureText: feature.value, - })) + })) : []; const { sku } = products[productIdx]; const { priceCode } = strategies[strategiesIdx]; @@ -32,21 +35,25 @@ const OfferToProductList = ({ offer, isLoggedIn, checkoutURL, loginURL, classNam actionText: strategies[strategiesIdx].summary, actionEvent: () => { Sales.clearCart() - .then(() => + .then(() => { Sales.addItemToCart([ { sku, priceCode, quantity: 1, }, - ]) - ) + ]); + const maxEndDate = Math.max(...offer?.campaigns?.map(c => c.validUntil)); + const liveCampaing = offer?.campaigns?.find(c => c.validUntil === null || c.validUntil === maxEndDate); + localStorage.setItem(ARCXP_CAMPAIGN, liveCampaing?.name); + }) .then(() => { if (isLoggedIn) { window.location.href = checkoutURL; return; } - window.location.href = `${loginURL}?redirect=${checkoutURL}`; + localStorage.setItem(ARCXP_CART, JSON.stringify({sku, priceCode})); + window.location.href = `${loginURL}&redirect=${checkoutURL}`; }); }, features, @@ -75,7 +82,7 @@ const OfferToProductList = ({ offer, isLoggedIn, checkoutURL, loginURL, classNam className={className} /> ))} - + ); }; diff --git a/blocks/subscriptions-block/components/OrderInformation/index.jsx b/blocks/subscriptions-block/components/OrderInformation/index.jsx new file mode 100644 index 0000000000..0b29fa355e --- /dev/null +++ b/blocks/subscriptions-block/components/OrderInformation/index.jsx @@ -0,0 +1,88 @@ +import React from "react"; + +import { usePhrases, Link, Heading, Paragraph, Stack } from "@wpmedia/arc-themes-components"; +import { FeatureDetails } from "../OfferCard"; +import currency from "../../utils/currency"; + +const OrderSummary = ({ orderDetails, className }) => { + const phrases = usePhrases(); + + return ( + + {phrases.t("checkout-block.order-summary")} + + +

{phrases.t("checkout-block.subtotal")}

+

{`${currency(orderDetails?.currency)}${orderDetails?.subtotal}`}

+
+ +

{phrases.t("checkout-block.salesTax")}

+

+ {orderDetails?.tax > 0 + ? `${currency(orderDetails?.currency)}${orderDetails?.tax}` + : "--"} +

+
+
+
+

{phrases.t("checkout-block.due-today")}

+

{`${currency(orderDetails?.currency)}${orderDetails?.total}`}

+
+
+ ); +}; + +const OrderInformation = ({ + offerURL, + showOfferURL, + showPriceDescription, + showProductFeatures, + orderDetails, + className, +}) => { + const phrases = usePhrases(); + + const ProductPriceDetails = ({ + details = [], + showPriceDescription, + showProductFeatures, + className, + }) => { + if (details?.items?.length) { + return details?.items?.map((item) => { + return ( + + {item.priceName} + {showPriceDescription && ( + + )} + {showProductFeatures && ( + + )} + + ); + }); + } + return null; + }; + + return ( +
+ + {showOfferURL && ( + {phrases.t("checkout-block.view-subscription-offers")} + )} + +
+ ); +}; + +export default OrderInformation; diff --git a/blocks/subscriptions-block/components/OrderInformation/index.test.js b/blocks/subscriptions-block/components/OrderInformation/index.test.js new file mode 100644 index 0000000000..74712e1e4f --- /dev/null +++ b/blocks/subscriptions-block/components/OrderInformation/index.test.js @@ -0,0 +1,104 @@ +import React from "react"; +import { screen, render, within } from "@testing-library/react"; +import "@testing-library/jest-dom"; +import OrderInformation from "./index"; +import currency from "../../utils/currency"; + + +jest.mock("@arc-publishing/sdk-identity", () => ({ + __esModule: true, + default: { + apiOrigin: "", + options: jest.fn(), + }, +})); + +jest.mock("fusion:properties", () => + jest.fn(() => ({ + api: { + identity: { + origin: "https://corecomponents-arc-demo-3-prod.api.cdn.arcpublishing.com", + }, + retail: { + origin: "https://corecomponents-arc-demo-3-prod.api.cdn.arcpublishing.com", + endpoint: "/retail/public/v1/offer/live/", + }, + }, + })), +); + +jest.mock("fusion:context", () => ({ + __esModule: true, + useFusionContext: () => ({ + arcSite: "Test Site", + }), +})); + +const orderDetails = { + currency: "COP", + shipping: 0, + subtotal: 20000, + tax: 100, + taxSupported: true, + total: 20100, + items: [ + { + name: "COP Currency", + price: 20000, + priceCode: "Q6R7UO", + priceDescription: "

with tax description price

", + priceName: "All access Annual", + priceSummary: "

with tax summary price

", + productDescription: "

COP Currency description

", + quantity: 1, + shortDescription: "

COP Currency description

", + sku: "0987", + subtotal: 20100, + tax: 0, + taxInclusive: undefined, + total: 20000, + productAttributes: [ + { + featureText: "

Unlimited access to The Daily Intelligencer

", + }, + { + featureText: "

Save $40

", + }, + { + featureText: "

A bonus subscription to share

", + }, + ], + }, + ], +}; + +describe("OfferCard", () => { + it("renders order info", ()=>{ + render(); + expect(screen.getByText(orderDetails?.items?.[0]?.priceName)).toBeVisible(); + expect(screen.getByText("checkout-block.order-summary")).toBeVisible(); + expect(screen.getByText("checkout-block.subtotal")).toBeVisible(); + expect(screen.getByText(`${currency(orderDetails?.currency)}${orderDetails?.subtotal}`)).toBeVisible(); + expect(screen.getByText("checkout-block.salesTax")).toBeVisible(); + expect(screen.getByText(`${currency(orderDetails?.currency)}${orderDetails?.total}`)).toBeVisible(); + expect(screen.getByText("checkout-block.due-today")).toBeVisible(); + }); + + it("renders price description", ()=>{ + render(); + expect(screen.getByText("with tax description price")).toBeVisible(); + }); + + it("renders features", () => { + render(); + const list = screen.getByRole("list") + const { getAllByRole } = within(list) + const items = getAllByRole("listitem") + expect(items.length).toBe(3); + }); + + it("renders link to offer", ()=>{ + render(); + expect(screen.getByRole('link', { name: 'checkout-block.view-subscription-offers' })).toHaveAttribute('href', '/offer/'); + }); +}); diff --git a/blocks/subscriptions-block/components/useOrder.jsx b/blocks/subscriptions-block/components/useOrder.jsx new file mode 100644 index 0000000000..3e0fc1fc26 --- /dev/null +++ b/blocks/subscriptions-block/components/useOrder.jsx @@ -0,0 +1,114 @@ +import { useState, useEffect } from "react"; + +import { useSales } from "@wpmedia/arc-themes-components"; +import { ARCXP_CAMPAIGN } from "./OfferToProductList"; +import useOffer from "./useOffer"; + +const useOrder = (orderNumber) => { + const { Sales } = useSales(); + + const [order, setOrder] = useState(); + const [orderDetails, setOrderDetails] = useState(); + const [cart, setCart] = useState(); + const [cartDetails, setCartDetails] = useState(); + const [error, setError] = useState(); + + const [campaignName, setCampaignName] = useState(); + + const { offer } = useOffer({ + campaignCode: campaignName, + }); + + useEffect(() => { + const getCart = async () => { + const cart = await Sales.getCart(); + setCart(cart); + }; + getCart(); + + const campaignName = localStorage.getItem(ARCXP_CAMPAIGN); + setCampaignName(campaignName); + }, []); + + useEffect(() => { + const getOrder = async () => { + try{ + const order = await Sales.getOrderDetails(orderNumber); + setOrder(order); + }catch(e){ + setError(e); + } + }; + if (orderNumber) { + getOrder(); + } + }, [orderNumber]); + + useEffect(() => { + const getProductPriceDetailsFromOffer = (sku, priceCode) => { + let productAttributes; + let productDescription; + let priceName; + let priceDescription; + let priceSummary; + let rates; + + const productBySku = offer?.products?.find((item) => item?.sku === sku); + + if (productBySku) { + productAttributes = + typeof productBySku?.attributes !== "undefined" && productBySku?.attributes?.length !== 0 + ? productBySku?.attributes.map((feature) => ({ + featureText: feature.value, + })) + : []; + productDescription = productBySku?.description; + const pricingStrategy = productBySku?.pricingStrategies?.find((price)=> price?.priceCode === priceCode); + + priceName = pricingStrategy?.name; + priceDescription = pricingStrategy?.description; + priceSummary = pricingStrategy?.summary; + rates = pricingStrategy?.rates; + } + + return { + productAttributes, + productDescription, + priceName, + priceDescription, + priceSummary, + rates, + }; + }; + + if (offer) { + if (order) { + const itemsDetail = order?.items?.map((item) => { + const { productAttributes, productDescription, priceName, priceDescription, priceSummary, rates } = getProductPriceDetailsFromOffer( + item?.sku, + item?.priceCode, + ); + return { ...item, productAttributes, productDescription, priceName, priceDescription, priceSummary, rates}; + }); + setOrderDetails({...order, items: itemsDetail}) + } else if (cart) { + const itemsDetail = cart?.items?.map((item) => { + const { productAttributes, productDescription, priceName, priceDescription, priceSummary, rates, taxInclusive } = getProductPriceDetailsFromOffer( + item?.sku, + item?.priceCode, + ); + return { ...item, productAttributes, productDescription, priceName, priceDescription, priceSummary, rates, taxInclusive}; + }); + setCartDetails({...cart, items: itemsDetail}) + } + } + }, [order, cart, offer]); + + return { + cartDetails, + orderDetails, + error + }; +}; + +export default useOrder; diff --git a/blocks/subscriptions-block/components/usePaymentRedirect.jsx b/blocks/subscriptions-block/components/usePaymentRedirect.jsx index d1806039b3..570d6a4aa4 100644 --- a/blocks/subscriptions-block/components/usePaymentRedirect.jsx +++ b/blocks/subscriptions-block/components/usePaymentRedirect.jsx @@ -1,5 +1,5 @@ import { useState, useEffect } from "react"; -import useSales from "./useSales"; +import { useSales } from "@wpmedia/arc-themes-components"; import { LABEL_ORDER_NUMBER_PAYPAL } from "../features/checkout/default"; export const usePaymentRedirect = ( diff --git a/blocks/subscriptions-block/intl.json b/blocks/subscriptions-block/intl.json index 07358dc45b..9f22f06403 100644 --- a/blocks/subscriptions-block/intl.json +++ b/blocks/subscriptions-block/intl.json @@ -6848,5 +6848,14 @@ "vi": "Cập nhật phương thức thanh toán", "zh-CN": "更新付款方式", "zh-TW": "更新付款方式" + }, + "checkout-block.view-subscription-offers": { + "en": "View subscription offers" + }, + "checkout-block.subtotal": { + "en": "Subtotal" + }, + "checkout-block.salesTax": { + "en": "Sales tax" } } \ No newline at end of file diff --git a/blocks/subscriptions-block/themes/news.json b/blocks/subscriptions-block/themes/news.json index a1223d662f..5c8940e0c7 100644 --- a/blocks/subscriptions-block/themes/news.json +++ b/blocks/subscriptions-block/themes/news.json @@ -401,6 +401,126 @@ } } }, + "checkout-order-card": { + "styles": { + "default": { + "display": "flex", + "flex-direction": "column", + "padding-block-start": "var(--global-spacing-4)", + "padding-inline-end": "var(--global-spacing-4)", + "padding-block-end": "var(--global-spacing-4)", + "padding-inline-start": "var(--global-spacing-4)", + "gap": "var(--global-spacing-5)", + "column-gap": "var(--global-spacing-5)", + "components": { + "link": { + "color": "var(--text-color)", + "text-decoration": "underline", + "font-size": "var(--global-font-size-4)", + "font-weight": "var(--global-font-weight-4)" + } + } + } + } + }, + "checkout-order-card-productPrice": { + "styles": { + "default": { + "display": "flex", + "flex-direction": "column", + "gap": "var(--global-spacing-5)", + "column-gap": "var(--global-spacing-5)", + "components": { + "heading": { + "font-size": "var(--global-font-size-9)", + "font-weight": "var(--global-font-weight-7)", + "line-height": "var(--global-line-height-6)" + } + } + } + } + }, + "checkout-order-card--features": { + "styles": { + "default": { + "align-self": "baseline" + } + } + }, + "checkout-order-card--features--item": { + "styles": { + "default": { + "align-items": "center", + "display": "flex", + "margin-block-end": "var(--global-spacing-2)", + "components": { + "icon": { + "fill": "var(--status-color-success)", + "display": "flex", + "inline-size": "var(--global-spacing-4)", + "block-size": "var(--global-spacing-4)", + "margin-block-start": "0", + "margin-inline-end": "var(--global-spacing-2)", + "margin-block-end": "0", + "margin-inline-start": "0" + } + } + } + } + }, + "checkout-order-card--summary": { + "styles": { + "default": { + "gap": "var(--global-spacing-2)", + "components": { + "heading": { + "font-family": "var(--font-family-secondary)", + "font-weight": "var(--global-font-weight-7)", + "font-size": "var(--global-font-size-4)", + "padding-block-end": "var(--global-spacing-2)", + "border-block-end-width": "1px", + "border-block-end-style": "solid", + "border-block-end-color": "var(--border-color)" + } + } + } + } + }, + "checkout-order-card--summary--dueToday": { + "styles": { + "default": { + "display": "flex", + "justify-content": "space-between", + "padding-block-start": "var(--global-spacing-2)", + "border-block-start-width": "1px", + "border-block-start-style": "solid", + "border-block-start-color": "var(--border-color)", + "font-family": "var(--font-family-secondary)", + "font-weight": "var(--global-font-weight-7)", + "font-size": "var(--global-font-size-7)", + "inline-size": "100%", + "align-items": "flex-start" + } + } + }, + "checkout-order-card--summary--details":{ + "styles": { + "default": { + "gap": "var(--global-spacing-2)" + } + } + }, + "checkout-order-card--summary--details--item": { + "styles": { + "default": { + "flex-direction": "row", + "justify-content": "space-between", + "font-family": "var(--font-family-primary)", + "font-weight": "var(--global-font-weight-4)", + "font-size": "var(--global-font-size-3)" + } + } + }, "offer": { "styles": { "default": { diff --git a/locale/en.json b/locale/en.json index cb45782a46..77ed7228a4 100644 --- a/locale/en.json +++ b/locale/en.json @@ -455,6 +455,7 @@ "checkout-block.Zambia": "Zambia", "checkout-block.Zimbabwe": "Zimbabwe", "checkout-block.back-to-offer-page": "Back to subscription selection", + "checkout-block.view-subscription-offers": "View subscription offers", "checkout-block.cardNumber": "Card number", "checkout-block.cardholderName": "Name on card", "checkout-block.cardholderName-requirements": "Please enter name on card", @@ -463,6 +464,8 @@ "checkout-block.country": "Country or region", "checkout-block.country-requirements": "Please select country or region", "checkout-block.due-today": "Due today", + "checkout-block.subtotal": "Subtotal", + "checkout-block.salesTax": "Sales tax", "checkout-block.email": "Email address", "checkout-block.email-requirements": "Please enter a valid email address", "checkout-block.empty-cart-message": "Select from one of our offers", From 4ed45d058ffc0eb46291bcca26d4361d06800466 Mon Sep 17 00:00:00 2001 From: LauraPinilla Date: Fri, 29 Mar 2024 13:51:22 -0600 Subject: [PATCH 2/6] linting errors --- .../components/OfferToProductList/index.jsx | 5 +- .../{index.test-ignore.jsx => index.test.jsx} | 23 +-- .../components/OrderInformation/index.jsx | 57 ++++--- .../components/OrderInformation/index.test.js | 104 ------------- .../OrderInformation/index.test.jsx | 141 ++++++++++++++++++ .../components/useOrder.jsx | 14 +- 6 files changed, 193 insertions(+), 151 deletions(-) rename blocks/subscriptions-block/components/OfferToProductList/{index.test-ignore.jsx => index.test.jsx} (93%) delete mode 100644 blocks/subscriptions-block/components/OrderInformation/index.test.js create mode 100644 blocks/subscriptions-block/components/OrderInformation/index.test.jsx diff --git a/blocks/subscriptions-block/components/OfferToProductList/index.jsx b/blocks/subscriptions-block/components/OfferToProductList/index.jsx index 83b72bcc66..8ceb23ee03 100644 --- a/blocks/subscriptions-block/components/OfferToProductList/index.jsx +++ b/blocks/subscriptions-block/components/OfferToProductList/index.jsx @@ -1,7 +1,7 @@ import React from "react"; import PropTypes from "@arc-fusion/prop-types"; -import useSales from "../useSales"; import { Grid } from "@wpmedia/arc-themes-components"; +import useSales from "../useSales"; import OfferCard from "../OfferCard"; export const ARCXP_CART = 'ArcXP_cart'; @@ -43,7 +43,8 @@ const OfferToProductList = ({ offer, isLoggedIn, checkoutURL, loginURL, classNam quantity: 1, }, ]); - const maxEndDate = Math.max(...offer?.campaigns?.map(c => c.validUntil)); + const allValidUntil = offer?.campaigns?.map(c => c.validUntil !== undefined && !Number.isNaN(c.validUntil)); + const maxEndDate = allValidUntil.length ? Math.max(allValidUntil) : null; const liveCampaing = offer?.campaigns?.find(c => c.validUntil === null || c.validUntil === maxEndDate); localStorage.setItem(ARCXP_CAMPAIGN, liveCampaing?.name); }) diff --git a/blocks/subscriptions-block/components/OfferToProductList/index.test-ignore.jsx b/blocks/subscriptions-block/components/OfferToProductList/index.test.jsx similarity index 93% rename from blocks/subscriptions-block/components/OfferToProductList/index.test-ignore.jsx rename to blocks/subscriptions-block/components/OfferToProductList/index.test.jsx index 8aceae2519..0461c82f6b 100644 --- a/blocks/subscriptions-block/components/OfferToProductList/index.test-ignore.jsx +++ b/blocks/subscriptions-block/components/OfferToProductList/index.test.jsx @@ -1,5 +1,5 @@ import React from "react"; -import { act, render } from "@testing-library/react"; +import { act, render, waitFor } from "@testing-library/react"; import OfferToProductList from "./index"; import useOffer from "../useOffer"; import OfferCard from "../OfferCard"; @@ -177,31 +177,34 @@ jest.mock("fusion:properties", () => script: "https://corecomponents-the-gazette-prod.cdn.arcpublishing.com/arc/subs/p.min.js", }, }, - })) + })), ); +jest.mock("../OfferCard", () => { + return jest.fn(() => null); + }); + jest.mock("@arc-publishing/sdk-sales"); jest.mock("../../components/useOffer"); + useOffer.mockReturnValue({ offer: sampleOffer, fetchOffer: () => sampleOffer, }); describe("The OfferToProductList component", () => { - it("renders the correct number of offer cards", () => { - const { container } = render( + it("renders the correct number of offer cards", async () => { + render( + />, ); - act(() => { - container.setProps({}); - }); + const mockedChildComponent = OfferCard; - expect(container.find(OfferCard)).toHaveLength(4); + await waitFor(() => expect(mockedChildComponent).toHaveBeenCalledTimes(4)); }); -}); \ No newline at end of file +}); diff --git a/blocks/subscriptions-block/components/OrderInformation/index.jsx b/blocks/subscriptions-block/components/OrderInformation/index.jsx index 0b29fa355e..77e83e6970 100644 --- a/blocks/subscriptions-block/components/OrderInformation/index.jsx +++ b/blocks/subscriptions-block/components/OrderInformation/index.jsx @@ -32,7 +32,33 @@ const OrderSummary = ({ orderDetails, className }) => { ); }; +const ProductPriceDetails = ({ + details = [], + showPriceDescription, + showProductFeatures, + className, +}) => { + if (details?.items?.length) { + return details?.items?.map((item) => + + {item.priceName} + {showPriceDescription && ( + + )} + {showProductFeatures && ( + + )} + + ); + } + return null; +}; + const OrderInformation = ({ + id, offerURL, showOfferURL, showPriceDescription, @@ -41,36 +67,9 @@ const OrderInformation = ({ className, }) => { const phrases = usePhrases(); - - const ProductPriceDetails = ({ - details = [], - showPriceDescription, - showProductFeatures, - className, - }) => { - if (details?.items?.length) { - return details?.items?.map((item) => { - return ( - - {item.priceName} - {showPriceDescription && ( - - )} - {showProductFeatures && ( - - )} - - ); - }); - } - return null; - }; - + return ( -
+
({ - __esModule: true, - default: { - apiOrigin: "", - options: jest.fn(), - }, -})); - -jest.mock("fusion:properties", () => - jest.fn(() => ({ - api: { - identity: { - origin: "https://corecomponents-arc-demo-3-prod.api.cdn.arcpublishing.com", - }, - retail: { - origin: "https://corecomponents-arc-demo-3-prod.api.cdn.arcpublishing.com", - endpoint: "/retail/public/v1/offer/live/", - }, - }, - })), -); - -jest.mock("fusion:context", () => ({ - __esModule: true, - useFusionContext: () => ({ - arcSite: "Test Site", - }), -})); - -const orderDetails = { - currency: "COP", - shipping: 0, - subtotal: 20000, - tax: 100, - taxSupported: true, - total: 20100, - items: [ - { - name: "COP Currency", - price: 20000, - priceCode: "Q6R7UO", - priceDescription: "

with tax description price

", - priceName: "All access Annual", - priceSummary: "

with tax summary price

", - productDescription: "

COP Currency description

", - quantity: 1, - shortDescription: "

COP Currency description

", - sku: "0987", - subtotal: 20100, - tax: 0, - taxInclusive: undefined, - total: 20000, - productAttributes: [ - { - featureText: "

Unlimited access to The Daily Intelligencer

", - }, - { - featureText: "

Save $40

", - }, - { - featureText: "

A bonus subscription to share

", - }, - ], - }, - ], -}; - -describe("OfferCard", () => { - it("renders order info", ()=>{ - render(); - expect(screen.getByText(orderDetails?.items?.[0]?.priceName)).toBeVisible(); - expect(screen.getByText("checkout-block.order-summary")).toBeVisible(); - expect(screen.getByText("checkout-block.subtotal")).toBeVisible(); - expect(screen.getByText(`${currency(orderDetails?.currency)}${orderDetails?.subtotal}`)).toBeVisible(); - expect(screen.getByText("checkout-block.salesTax")).toBeVisible(); - expect(screen.getByText(`${currency(orderDetails?.currency)}${orderDetails?.total}`)).toBeVisible(); - expect(screen.getByText("checkout-block.due-today")).toBeVisible(); - }); - - it("renders price description", ()=>{ - render(); - expect(screen.getByText("with tax description price")).toBeVisible(); - }); - - it("renders features", () => { - render(); - const list = screen.getByRole("list") - const { getAllByRole } = within(list) - const items = getAllByRole("listitem") - expect(items.length).toBe(3); - }); - - it("renders link to offer", ()=>{ - render(); - expect(screen.getByRole('link', { name: 'checkout-block.view-subscription-offers' })).toHaveAttribute('href', '/offer/'); - }); -}); diff --git a/blocks/subscriptions-block/components/OrderInformation/index.test.jsx b/blocks/subscriptions-block/components/OrderInformation/index.test.jsx new file mode 100644 index 0000000000..bac4aabfbb --- /dev/null +++ b/blocks/subscriptions-block/components/OrderInformation/index.test.jsx @@ -0,0 +1,141 @@ +import React from "react"; +import { render, screen, within } from "@testing-library/react"; +import '@testing-library/jest-dom'; +import OrderInformation from "./index"; +import currency from "../../utils/currency"; + +const orderDetails = { + currency: "COP", + shipping: 0, + subtotal: 20000, + tax: 100, + taxSupported: true, + total: 20100, + items: [ + { + name: "COP Currency", + price: 20000, + priceCode: "Q6R7UO", + priceDescription: "

with tax description price

", + priceName: "All access Annual", + priceSummary: "

with tax summary price

", + productDescription: "

COP Currency description

", + quantity: 1, + shortDescription: "

COP Currency description

", + sku: "0987", + subtotal: 20100, + tax: 0, + taxInclusive: undefined, + total: 20000, + productAttributes: [ + { + featureText: "

Unlimited access to The Daily Intelligencer

", + }, + { + featureText: "

Save $40

", + }, + { + featureText: "

A bonus subscription to share

", + }, + ], + }, + ], +}; + +jest.mock("@arc-publishing/sdk-identity", () => ({ + __esModule: true, + default: { + apiOrigin: "", + options: jest.fn(), + }, +})); + +jest.mock("fusion:properties", () => + jest.fn(() => ({ + api: { + identity: { + origin: "https://corecomponents-arc-demo-3-prod.api.cdn.arcpublishing.com", + }, + retail: { + origin: "https://corecomponents-arc-demo-3-prod.api.cdn.arcpublishing.com", + endpoint: "/retail/public/v1/offer/live/", + }, + }, + })), +); + +jest.mock("fusion:context", () => ({ + __esModule: true, + useFusionContext: () => ({ + arcSite: "Test Site", + }), +})); + +describe('Order Information component', () => { + it("renders order info", () => { + render( + , + ); + expect(screen.getByText(orderDetails?.items?.[0]?.priceName)).toBeVisible(); + expect(screen.getByText("checkout-block.order-summary")).toBeVisible(); + expect(screen.getByText("checkout-block.subtotal")).toBeVisible(); + expect( + screen.getByText(`${currency(orderDetails?.currency)}${orderDetails?.subtotal}`), + ).toBeVisible(); + expect(screen.getByText("checkout-block.salesTax")).toBeVisible(); + expect( + screen.getByText(`${currency(orderDetails?.currency)}${orderDetails?.total}`), + ).toBeVisible(); + expect(screen.getByText("checkout-block.due-today")).toBeVisible(); + }); + + it("renders price description", () => { + render( + , + ); + expect(screen.getByText("with tax description price")).toBeVisible(); + }); + + it("renders features", () => { + render( + , + ); + const list = screen.getByRole("list"); + const { getAllByRole } = within(list); + const items = getAllByRole("listitem"); + expect(items.length).toBe(3); + }); + + it("renders link to offer", () => { + render( + , + ); + expect( + screen.getByRole("link", { name: "checkout-block.view-subscription-offers" }), + ).toHaveAttribute("href", "/offer/"); + }); +}); diff --git a/blocks/subscriptions-block/components/useOrder.jsx b/blocks/subscriptions-block/components/useOrder.jsx index 3e0fc1fc26..44bffa0c9b 100644 --- a/blocks/subscriptions-block/components/useOrder.jsx +++ b/blocks/subscriptions-block/components/useOrder.jsx @@ -21,20 +21,21 @@ const useOrder = (orderNumber) => { useEffect(() => { const getCart = async () => { - const cart = await Sales.getCart(); - setCart(cart); + const currentCart = await Sales.getCart(); + setCart(currentCart); }; getCart(); - const campaignName = localStorage.getItem(ARCXP_CAMPAIGN); - setCampaignName(campaignName); + const campaignNameStored = localStorage.getItem(ARCXP_CAMPAIGN); + setCampaignName(campaignNameStored); + // eslint-disable-next-line }, []); useEffect(() => { const getOrder = async () => { try{ - const order = await Sales.getOrderDetails(orderNumber); - setOrder(order); + const currentOrder = await Sales.getOrderDetails(orderNumber); + setOrder(currentOrder); }catch(e){ setError(e); } @@ -42,6 +43,7 @@ const useOrder = (orderNumber) => { if (orderNumber) { getOrder(); } + // eslint-disable-next-line }, [orderNumber]); useEffect(() => { From 94c6dbb99970d404e1cee1a7d0c17ebed289cb12 Mon Sep 17 00:00:00 2001 From: LauraPinilla Date: Sun, 31 Mar 2024 20:58:00 -0600 Subject: [PATCH 3/6] fixing test --- .../OfferToProductList/index.test.jsx | 8 +- .../OrderInformation/index.test.jsx | 33 +- .../components/useOrder.jsx | 98 +++-- .../components/useOrder.test.jsx | 369 ++++++++++++++++++ .../components/usePaymentRedirect.jsx | 2 +- 5 files changed, 462 insertions(+), 48 deletions(-) create mode 100644 blocks/subscriptions-block/components/useOrder.test.jsx diff --git a/blocks/subscriptions-block/components/OfferToProductList/index.test.jsx b/blocks/subscriptions-block/components/OfferToProductList/index.test.jsx index 0461c82f6b..30c9ad38f7 100644 --- a/blocks/subscriptions-block/components/OfferToProductList/index.test.jsx +++ b/blocks/subscriptions-block/components/OfferToProductList/index.test.jsx @@ -1,5 +1,5 @@ import React from "react"; -import { act, render, waitFor } from "@testing-library/react"; +import {render, waitFor } from "@testing-library/react"; import OfferToProductList from "./index"; import useOffer from "../useOffer"; import OfferCard from "../OfferCard"; @@ -180,9 +180,9 @@ jest.mock("fusion:properties", () => })), ); -jest.mock("../OfferCard", () => { - return jest.fn(() => null); - }); +jest.mock("../OfferCard", () => + (jest.fn(() => null)) + ); jest.mock("@arc-publishing/sdk-sales"); jest.mock("../../components/useOffer"); diff --git a/blocks/subscriptions-block/components/OrderInformation/index.test.jsx b/blocks/subscriptions-block/components/OrderInformation/index.test.jsx index bac4aabfbb..e67cc26372 100644 --- a/blocks/subscriptions-block/components/OrderInformation/index.test.jsx +++ b/blocks/subscriptions-block/components/OrderInformation/index.test.jsx @@ -73,14 +73,15 @@ jest.mock("fusion:context", () => ({ describe('Order Information component', () => { it("renders order info", () => { + const showProductF = true; render( , ); expect(screen.getByText(orderDetails?.items?.[0]?.priceName)).toBeVisible(); @@ -97,25 +98,30 @@ describe('Order Information component', () => { }); it("renders price description", () => { + const showPriceDesc = true; + const showProductFeat = true; render( , ); expect(screen.getByText("with tax description price")).toBeVisible(); }); it("renders features", () => { + const showOfferUrl = true + const showPriceDesc = false; + const showProductFeat = true; render( , ); const list = screen.getByRole("list"); @@ -125,13 +131,16 @@ describe('Order Information component', () => { }); it("renders link to offer", () => { + const showOfferUrl = true + const showPriceDesc = false; + const showProductFeat = true; render( , ); expect( diff --git a/blocks/subscriptions-block/components/useOrder.jsx b/blocks/subscriptions-block/components/useOrder.jsx index 44bffa0c9b..0859413a5e 100644 --- a/blocks/subscriptions-block/components/useOrder.jsx +++ b/blocks/subscriptions-block/components/useOrder.jsx @@ -8,9 +8,9 @@ const useOrder = (orderNumber) => { const { Sales } = useSales(); const [order, setOrder] = useState(); - const [orderDetails, setOrderDetails] = useState(); + const [orderDetails, setOrderDetails] = useState(); const [cart, setCart] = useState(); - const [cartDetails, setCartDetails] = useState(); + const [cartDetails, setCartDetails] = useState(); const [error, setError] = useState(); const [campaignName, setCampaignName] = useState(); @@ -21,8 +21,14 @@ const useOrder = (orderNumber) => { useEffect(() => { const getCart = async () => { - const currentCart = await Sales.getCart(); - setCart(currentCart); + try { + const currentCart = await Sales.getCart(); + if(currentCart.items.length){ + setCart(currentCart); + } + } catch (e) { + setError(e); + } }; getCart(); @@ -33,10 +39,12 @@ const useOrder = (orderNumber) => { useEffect(() => { const getOrder = async () => { - try{ + try { const currentOrder = await Sales.getOrderDetails(orderNumber); - setOrder(currentOrder); - }catch(e){ + if(currentOrder?.items?.length){ + setOrder(currentOrder); + } + } catch (e) { setError(e); } }; @@ -49,10 +57,10 @@ const useOrder = (orderNumber) => { useEffect(() => { const getProductPriceDetailsFromOffer = (sku, priceCode) => { let productAttributes; - let productDescription; + let productDescription; let priceName; - let priceDescription; - let priceSummary; + let priceDescription; + let priceSummary; let rates; const productBySku = offer?.products?.find((item) => item?.sku === sku); @@ -64,21 +72,23 @@ const useOrder = (orderNumber) => { featureText: feature.value, })) : []; - productDescription = productBySku?.description; - const pricingStrategy = productBySku?.pricingStrategies?.find((price)=> price?.priceCode === priceCode); + productDescription = productBySku?.description; + const pricingStrategy = productBySku?.pricingStrategies?.find( + (price) => price?.priceCode === priceCode, + ); priceName = pricingStrategy?.name; - priceDescription = pricingStrategy?.description; - priceSummary = pricingStrategy?.summary; - rates = pricingStrategy?.rates; + priceDescription = pricingStrategy?.description; + priceSummary = pricingStrategy?.summary; + rates = pricingStrategy?.rates; } return { productAttributes, - productDescription, + productDescription, priceName, - priceDescription, - priceSummary, + priceDescription, + priceSummary, rates, }; }; @@ -86,22 +96,48 @@ const useOrder = (orderNumber) => { if (offer) { if (order) { const itemsDetail = order?.items?.map((item) => { - const { productAttributes, productDescription, priceName, priceDescription, priceSummary, rates } = getProductPriceDetailsFromOffer( - item?.sku, - item?.priceCode, - ); - return { ...item, productAttributes, productDescription, priceName, priceDescription, priceSummary, rates}; + const { + productAttributes, + productDescription, + priceName, + priceDescription, + priceSummary, + rates, + } = getProductPriceDetailsFromOffer(item?.sku, item?.priceCode); + return { + ...item, + productAttributes, + productDescription, + priceName, + priceDescription, + priceSummary, + rates, + }; }); - setOrderDetails({...order, items: itemsDetail}) + setOrderDetails({ ...order, items: itemsDetail }); } else if (cart) { const itemsDetail = cart?.items?.map((item) => { - const { productAttributes, productDescription, priceName, priceDescription, priceSummary, rates, taxInclusive } = getProductPriceDetailsFromOffer( - item?.sku, - item?.priceCode, - ); - return { ...item, productAttributes, productDescription, priceName, priceDescription, priceSummary, rates, taxInclusive}; + const { + productAttributes, + productDescription, + priceName, + priceDescription, + priceSummary, + rates, + taxInclusive, + } = getProductPriceDetailsFromOffer(item?.sku, item?.priceCode); + return { + ...item, + productAttributes, + productDescription, + priceName, + priceDescription, + priceSummary, + rates, + taxInclusive, + }; }); - setCartDetails({...cart, items: itemsDetail}) + setCartDetails({ ...cart, items: itemsDetail }); } } }, [order, cart, offer]); @@ -109,7 +145,7 @@ const useOrder = (orderNumber) => { return { cartDetails, orderDetails, - error + error, }; }; diff --git a/blocks/subscriptions-block/components/useOrder.test.jsx b/blocks/subscriptions-block/components/useOrder.test.jsx new file mode 100644 index 0000000000..cdb89f1695 --- /dev/null +++ b/blocks/subscriptions-block/components/useOrder.test.jsx @@ -0,0 +1,369 @@ +import React from "react"; + +import { useSales } from "@wpmedia/arc-themes-components"; +import { waitFor, renderHook } from "@testing-library/react"; + +import useOrder from "./useOrder"; +import useOffer from "./useOffer"; + +// Mock setTimeout and clearTimeout +jest.useFakeTimers(); + +jest.mock("@arc-publishing/sdk-identity", () => ({ + __esModule: true, + default: { + apiOrigin: "", + options: jest.fn(), + }, +})); + +jest.mock("fusion:properties", () => + jest.fn(() => ({ + api: { + identity: { + origin: "https://corecomponents-arc-demo-3-prod.api.cdn.arcpublishing.com", + }, + retail: { + origin: "https://corecomponents-arc-demo-3-prod.api.cdn.arcpublishing.com", + endpoint: "/retail/public/v1/offer/live/", + }, + }, + })), +); + +jest.mock("fusion:context", () => ({ + __esModule: true, + useFusionContext: () => ({ + arcSite: "Test Site", + }), +})); + +const cartDetail = { + total: 20000, + subtotal: 20000, + tax: 0, + shipping: 0, + currency: "COP", + taxSupported: true, + items: [ + { + sku: "0987", + quantity: 1, + shortDescription: "

COP Currency description

", + name: "COP Currency", + price: 20000, + tax: 0, + subtotal: 20000, + total: 20000, + priceCode: "Q6R7UO", + eventId: null, + ownerClientId: null, + attributes: [], + gift: false, + }, + ], +}; + +const mockSales = { + getCart: jest.fn(() => Promise.resolve(cartDetail)), + getOrderDetails: jest.fn(() => {}), +}; + +jest.mock("@wpmedia/arc-themes-components", () => ({ + ...jest.requireActual("@wpmedia/arc-themes-components"), + useIdentity: jest.fn(() => ({ + isInitialized: true, + })), + useSales: jest.fn(() => ({ + isInitialized: true, + Sales: { + ...mockSales, + }, + })), +})); + +const localStorageMock = (() => { + let store = {}; + + return { + getItem: (key) => store[key], + setItem: (key, value) => { + store[key] = value.toString(); + }, + clear: () => { + store = {}; + }, + }; +})(); + +const sampleOffer = { + name: "test_campaign", + disclaimerText: null, + largeImage: null, + mediumImage: null, + smallImage: null, + pageSubTitle: "

test_campaign

", + pageTitle: "

test_campaign

", + templateName: "test-template", + campaigns: [ + { + canRenew: true, + canRestart: true, + canStart: true, + name: "demoCampaign", + validFrom: 1710888000000, + validUntil: 1711060801000, + }, + ], + products: [ + { + sku: "0987", + description: "

COP Currency description

", + image: null, + imageAction: null, + name: "COP Currency", + thumbnail: null, + maxSubscriptionAssociations: 0, + attributes: [ + { + name: "test-style", + value: "

Unlimited access to The Daily Intelligencer

", + }, + { + name: "test-style", + value: "

Save $40

", + }, + { + name: "test-style", + value: "

A bonus subscription to share

", + }, + ], + pricingStrategies: [ + { + pricingStrategyId: 4186, + priceCode: "Q6R7UO", + name: "All access Annual", + description: "

with tax description price

", + gift: false, + summary: "

with tax summary price

", + currencyCode: "COP", + currencyDisplayFormat: "symbol", + currencyLocale: "es-CO", + rates: [ + { + amount: "20000.00", + billingCount: 1, + billingFrequency: "Month", + durationCount: 1, + duration: "UntilCancelled", + }, + ], + taxInclusive: false, + }, + { + pricingStrategyId: 4187, + priceCode: "Z53LGY", + name: "taxIncluded priceName", + description: "

tax included priceDescription

", + gift: false, + summary: "

tax included priceSummary

", + currencyCode: "COP", + currencyDisplayFormat: "symbol", + currencyLocale: "es-CO", + rates: [ + { + amount: "40000.00", + billingCount: 1, + billingFrequency: "Month", + durationCount: 1, + duration: "UntilCancelled", + }, + ], + taxInclusive: true, + }, + ], + defaultSwgProduct: false, + }, + ], + attributes: [], + default: true, +}; + +jest.mock("./useOffer"); +useOffer.mockReturnValue({ + offer: sampleOffer, + fetchOffer: () => sampleOffer, + isFetching: false, +}); + +const emptyCart = { + total: 0, + subtotal: 0, + tax: 0, + shipping: 0, + items: [], + currency: "USD", + taxSupported: true, +}; + +describe("The OfferToProductList component", () => { + beforeEach(() => { + Object.defineProperty(window, "localStorage", { + value: localStorageMock, + }); + localStorage.setItem("ArcXP_campaignName", "demoCampaign"); + }); + + it("User has a cart", async () => { + const { result } = renderHook(() => useOrder()); + + expect(result.current.cartDetails).toBe(undefined); + expect(result.current.orderDetails).toBe(undefined); + expect(result.current.error).toBe(undefined); + + const newCartDetails = { + ...cartDetail, + items: [ + { + ...cartDetail?.items?.[0], + productAttributes: [ + { featureText: "

Unlimited access to The Daily Intelligencer

" }, + { + featureText: "

Save $40

", + }, + { + featureText: "

A bonus subscription to share

", + }, + ], + productDescription: "

COP Currency description

", + priceName: "All access Annual", + priceDescription: "

with tax description price

", + priceSummary: "

with tax summary price

", + rates: [ + { + amount: "20000.00", + billingCount: 1, + billingFrequency: "Month", + durationCount: 1, + duration: "UntilCancelled", + }, + ], + }, + ], + }; + + await waitFor(() => expect(result.current.cartDetails).toEqual(newCartDetails)); + await waitFor(() => expect(result.current.orderDetails).toEqual(undefined)); + await waitFor(() => expect(result.current.error).toEqual(undefined)); + }); + + it("User has order", async () => { + + const orderDetail = { + total: 22000, + subtotal: 20000, + tax: 2000, + shipping: 0, + items: [ + { + sku: "0987", + quantity: 1, + shortDescription: "

COP Currency description

", + name: "COP Currency", + price: 20000, + tax: 2000, + subtotal: 20000, + total: 22000, + priceCode: "Q6R7UO", + eventId: null, + ownerClientId: null, + attributes: [], + gift: false, + }, + ], + currency: "COP", + orderNumber: "6OGJP2GGVJ7GT8L3", + status: "Pending", + email: "laurapinb@gmail.com", + phone: "NA", + firstName: "laura", + lastName: "pin", + orderDateUTC: 1711936392300, + taxDelegated: false, + paymentPending: false, + }; + + useSales.mockReturnValueOnce({ + isInitialized: true, + Sales: { + getCart: jest.fn(() => Promise.resolve(emptyCart)), + getOrderDetails: jest.fn(() => { + return Promise.resolve(orderDetail); + }), + }, + }); + + const newOrderDetail = { + ...orderDetail, + items: [ + { + ...orderDetail?.items?.[0], + productAttributes: [ + { featureText: "

Unlimited access to The Daily Intelligencer

" }, + { + featureText: "

Save $40

", + }, + { + featureText: "

A bonus subscription to share

", + }, + ], + productDescription: "

COP Currency description

", + priceName: "All access Annual", + priceDescription: "

with tax description price

", + priceSummary: "

with tax summary price

", + rates: [ + { + amount: "20000.00", + billingCount: 1, + billingFrequency: "Month", + durationCount: 1, + duration: "UntilCancelled", + }, + ], + }, + ], + } + + const { result } = renderHook(() => useOrder("6OGJP2GGVJ7GT8L3")); + + expect(result.current.cartDetails).toBe(undefined); + expect(result.current.orderDetails).toBe(undefined); + expect(result.current.error).toBe(undefined); + + await waitFor(() => expect(result.current.cartDetails).toEqual(undefined)); + await waitFor(() => expect(result.current.orderDetails).toEqual(newOrderDetail)); + await waitFor(() => expect(result.current.error).toEqual(undefined)); + }); + + it("getOrderDetail is returning an error", async () => { + + const error = {code: '200019', message: 'Access Denied'}; + useSales.mockReturnValueOnce({ + isInitialized: true, + Sales: { + getCart: jest.fn(() => Promise.resolve(emptyCart)), + getOrderDetails: jest.fn().mockRejectedValueOnce(error) + }, + }); + + const { result } = renderHook(() => useOrder("6OGJP2GGVJ7GT8L3")); + + expect(result.current.cartDetails).toBe(undefined); + expect(result.current.orderDetails).toBe(undefined); + expect(result.current.error).toBe(undefined); + + await waitFor(() => expect(result.current.cartDetails).toEqual(undefined)); + await waitFor(() => expect(result.current.orderDetails).toEqual(undefined)); + await waitFor(() => expect(result.current.error).toEqual(error)); + }) +}); diff --git a/blocks/subscriptions-block/components/usePaymentRedirect.jsx b/blocks/subscriptions-block/components/usePaymentRedirect.jsx index 570d6a4aa4..d1806039b3 100644 --- a/blocks/subscriptions-block/components/usePaymentRedirect.jsx +++ b/blocks/subscriptions-block/components/usePaymentRedirect.jsx @@ -1,5 +1,5 @@ import { useState, useEffect } from "react"; -import { useSales } from "@wpmedia/arc-themes-components"; +import useSales from "./useSales"; import { LABEL_ORDER_NUMBER_PAYPAL } from "../features/checkout/default"; export const usePaymentRedirect = ( From 96e3ee10355d99024c92a54139f1796da6d2d069 Mon Sep 17 00:00:00 2001 From: LauraPinilla Date: Sun, 31 Mar 2024 22:08:59 -0600 Subject: [PATCH 4/6] fixing test --- .../OrderInformation/index.test.jsx | 24 +++++++++++++++++++ .../utils/currency.test.jsx | 4 ++++ 2 files changed, 28 insertions(+) diff --git a/blocks/subscriptions-block/components/OrderInformation/index.test.jsx b/blocks/subscriptions-block/components/OrderInformation/index.test.jsx index e67cc26372..270cb3d827 100644 --- a/blocks/subscriptions-block/components/OrderInformation/index.test.jsx +++ b/blocks/subscriptions-block/components/OrderInformation/index.test.jsx @@ -71,6 +71,8 @@ jest.mock("fusion:context", () => ({ }), })); +const className = 'checkout'; + describe('Order Information component', () => { it("renders order info", () => { const showProductF = true; @@ -82,6 +84,7 @@ describe('Order Information component', () => { showOfferURL={false} showPriceDescription={false} showProductFeatures={showProductF} + className={className} />, ); expect(screen.getByText(orderDetails?.items?.[0]?.priceName)).toBeVisible(); @@ -95,6 +98,9 @@ describe('Order Information component', () => { screen.getByText(`${currency(orderDetails?.currency)}${orderDetails?.total}`), ).toBeVisible(); expect(screen.getByText("checkout-block.due-today")).toBeVisible(); + + const headingElement = document.getElementsByClassName('c-heading'); + expect(headingElement.length).toBe(2) }); it("renders price description", () => { @@ -146,5 +152,23 @@ describe('Order Information component', () => { expect( screen.getByRole("link", { name: "checkout-block.view-subscription-offers" }), ).toHaveAttribute("href", "/offer/"); + }); + + it("items is empty", ()=>{ + const showOfferUrl = true + const showPriceDesc = false; + const showProductFeat = true; + render( + , + ); + const headingElement = document.getElementsByClassName('c-heading'); + expect(headingElement.length).toBe(1) + }) }); diff --git a/blocks/subscriptions-block/utils/currency.test.jsx b/blocks/subscriptions-block/utils/currency.test.jsx index bd73f71416..1905a22b98 100644 --- a/blocks/subscriptions-block/utils/currency.test.jsx +++ b/blocks/subscriptions-block/utils/currency.test.jsx @@ -8,4 +8,8 @@ describe("Currency", () => { it("returns null if currency is not supported", () => { expect(currency("HKD")).toBe(null); }); + + it("returns null if currency is sent", () => { + expect(currency()).toBe(null); + }); }); \ No newline at end of file From 23d653bb0f9d95e6ff1ee3adcbb7eba1bf94abea Mon Sep 17 00:00:00 2001 From: LauraPinilla Date: Mon, 1 Apr 2024 01:24:37 -0600 Subject: [PATCH 5/6] fixing test --- .../{index.test-ignore.jsx => index.test.jsx} | 35 ++-- .../components/useOffer.test-ignore.jsx | 195 ------------------ .../components/useOffer.test.jsx | 184 +++++++++++++++++ .../components/useOrder.jsx | 2 +- .../components/useOrder.test.jsx | 38 +++- 5 files changed, 234 insertions(+), 220 deletions(-) rename blocks/subscriptions-block/components/OfferCard/{index.test-ignore.jsx => index.test.jsx} (61%) delete mode 100644 blocks/subscriptions-block/components/useOffer.test-ignore.jsx create mode 100644 blocks/subscriptions-block/components/useOffer.test.jsx diff --git a/blocks/subscriptions-block/components/OfferCard/index.test-ignore.jsx b/blocks/subscriptions-block/components/OfferCard/index.test.jsx similarity index 61% rename from blocks/subscriptions-block/components/OfferCard/index.test-ignore.jsx rename to blocks/subscriptions-block/components/OfferCard/index.test.jsx index 24351184e1..5c04768816 100644 --- a/blocks/subscriptions-block/components/OfferCard/index.test-ignore.jsx +++ b/blocks/subscriptions-block/components/OfferCard/index.test.jsx @@ -1,5 +1,5 @@ import React from "react"; -import { render } from "@testing-library/react"; +import { render, screen } from "@testing-library/react"; import OfferCard from "."; const props = { @@ -24,51 +24,58 @@ describe("OfferCard", () => { expect(screen.getByRole("button")).not.toBeNull(); - const ul = getByRole("list"); - expect(ul).toBeInTheDocument(); + const ul = screen.getByRole("list"); + expect(ul.childElementCount).toBe(2); + + const ulByClass = document.getElementsByClassName("test-block__card--features--feature-item"); + expect(ulByClass.length).toBe(2); expect(screen.getByText(props.features[0].featureText)).not.toBeNull(); expect(screen.getByText(props.features[1].featureText)).not.toBeNull(); }); it("does not render headline if not present", () => { - const { container } = render( + render( , ); - - expect(container.querySelector(".b-offer__card h1")).not.toBeInTheDocument(); + const headingElement = document.getElementsByClassName('.b-offer__card h1'); + expect(headingElement.length).toBe(0) }); it("does not render subHeadline if not present", () => { - const { container } = render( + render( , ); - expect(container.querySelector(".b-offer__card p")).not.toBeInTheDocument(); + const headingElement = document.getElementsByClassName('.b-offer__card p'); + expect(headingElement.length).toBe(0) }); it("does not render button if no actionText and no ActionEvent", () => { render(); - expect(screen.getByRole("button")).not.toBeNull(); + const button = screen.queryByRole("button"); + expect(button).toBeNull(); }); it("does not render button if no actionText", () => { render(); - expect(screen.getByRole("button")).not.toBeNull(); + const button = screen.queryByRole("button"); + expect(button).toBeNull(); }); it("does not render button if no actionEvent", () => { render(); - expect(screen.getByRole("button")).not.toBeNull(); + const button = screen.queryByRole("button"); + expect(button).toBeNull(); }); it("does not render features", () => { - const { container } = render(); + render(); - const features = expect(container.querySelector(".b-offer__card--features li")); - expect(features.length).toBe(0); + const ulByClass = document.getElementsByClassName("test-block__card--features--feature-item"); + expect(ulByClass.length).toBe(0); }); }); diff --git a/blocks/subscriptions-block/components/useOffer.test-ignore.jsx b/blocks/subscriptions-block/components/useOffer.test-ignore.jsx deleted file mode 100644 index 6c9d489c98..0000000000 --- a/blocks/subscriptions-block/components/useOffer.test-ignore.jsx +++ /dev/null @@ -1,195 +0,0 @@ -function sleep(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - -jest.mock("fusion:context", () => ({ - useFusionContext: jest.fn(() => ({ - arcSite: "arc-demo-3", - })), -})); - -jest.mock("fusion:properties", () => - jest.fn(() => ({ - api: { - retail: { - origin: "https://corecomponents-arc-demo-3-prod.api.cdn.arcpublishing.com", - endpoint: "/retail/public/v1/offer/live/", - }, - }, - })) -); - -const testOfferResponse = { - name: "Premium Offer", - disclaimerText: null, - largeImage: null, - mediumImage: null, - smallImage: null, - pageSubTitle: "

Free trial: first month free, then $10/mo

", - pageTitle: "

Default subscription

", - templateName: "bottom-drawer", - campaigns: [ - { - canRenew: true, - canRestart: true, - canStart: true, - name: "augpromo", - validFrom: 1629216600000, - validUntil: 1630468800000, - }, - ], - products: [ - { - sku: "premium", - description: "

Get access to premium content

", - image: null, - imageAction: null, - name: "Premium Content", - thumbnail: null, - maxSubscriptionAssociations: 0, - attributes: [ - { - name: "p", - value: "

get access to sports, business, cooking sections

", - }, - ], - pricingStrategies: [ - { - pricingStrategyId: 1169, - priceCode: "1B1HCQ", - name: "Premium free trial", - description: "

free trial price

", - gift: false, - summary: null, - currencyCode: "USD", - currencyDisplayFormat: "symbol", - currencyLocale: "en-US", - rates: [ - { - amount: "0.00", - billingCount: 1, - billingFrequency: "Month", - durationCount: 1, - duration: "Month", - }, - { - amount: "10.00", - billingCount: 1, - billingFrequency: "Month", - durationCount: 1, - duration: "UntilCancelled", - }, - ], - taxInclusive: false, - }, - { - pricingStrategyId: 1168, - priceCode: "H7SCJB", - name: "premium all access", - description: "

Get access to premium content for a low rate

", - gift: false, - summary: null, - currencyCode: "USD", - currencyDisplayFormat: "symbol", - currencyLocale: "en-US", - rates: [ - { - amount: "10.00", - billingCount: 1, - billingFrequency: "Month", - durationCount: 1, - duration: "UntilCancelled", - }, - ], - taxInclusive: false, - }, - ], - defaultSwgProduct: false, - }, - ], - attributes: [], - default: true, -}; - -function TestOfferComponent({ code }) { - const { offer, isFetching, error } = useOffer({ - campaignCode: code, - }); - if (error) { - return
Error: {error}
; - } - if (isFetching || !offer) { - return
Fetching
; - } - return
; -} - -global.fetch = jest.fn(() => - Promise.resolve({ - json: () => Promise.resolve(testOfferResponse), - }) -); - -let container = null; -beforeEach(() => { - jest.clearAllMocks(); - fetch.mockClear(); - container = document.createElement("div"); - document.body.appendChild(container); -}); - -afterEach(() => { - unmountComponentAtNode(container); - container.remove(); - container = null; -}); - -it("useOffer hook runs correctly", async () => { - act(() => { - render( - , - container - ); - }); - expect(container.textContent).toBe("Fetching"); - await act(() => sleep(500)); - expect(container.textContent).toBe("Default subscription"); -}); - -it("use offer uses fallback default string", async () => { - act(() => { - render( - , - container - ); - }); - expect(container.textContent).toBe("Fetching"); - await act(() => sleep(500)); - expect(container.textContent).toBe("Default subscription"); -}); - -it("useOffer hook handles an error state during fetch", async () => { - global.fetch = jest.fn(() => Promise.reject(new Error("Error #2"))); - - act(() => { - render( - , - container - ); - }); - expect(container.textContent).toBe("Fetching"); - await act(() => sleep(500)); - expect(container.textContent).toContain("Error #2"); -}); \ No newline at end of file diff --git a/blocks/subscriptions-block/components/useOffer.test.jsx b/blocks/subscriptions-block/components/useOffer.test.jsx new file mode 100644 index 0000000000..d139d64003 --- /dev/null +++ b/blocks/subscriptions-block/components/useOffer.test.jsx @@ -0,0 +1,184 @@ +import { waitFor, renderHook } from "@testing-library/react"; +import useOffer from "./useOffer"; + +jest.mock("fusion:context", () => ({ + useFusionContext: jest.fn(() => ({ + arcSite: "arc-demo-3", + })), +})); + +const testOfferResponse = { + name: "Premium Offer", + disclaimerText: null, + largeImage: null, + mediumImage: null, + smallImage: null, + pageSubTitle: "

Free trial: first month free, then $10/mo

", + pageTitle: "

Default subscription

", + templateName: "bottom-drawer", + campaigns: [ + { + canRenew: true, + canRestart: true, + canStart: true, + name: "augpromo", + validFrom: 1629216600000, + validUntil: 1630468800000, + }, + ], + products: [ + { + sku: "premium", + description: "

Get access to premium content

", + image: null, + imageAction: null, + name: "Premium Content", + thumbnail: null, + maxSubscriptionAssociations: 0, + attributes: [ + { + name: "p", + value: "

get access to sports, business, cooking sections

", + }, + ], + pricingStrategies: [ + { + pricingStrategyId: 1169, + priceCode: "1B1HCQ", + name: "Premium free trial", + description: "

free trial price

", + gift: false, + summary: null, + currencyCode: "USD", + currencyDisplayFormat: "symbol", + currencyLocale: "en-US", + rates: [ + { + amount: "0.00", + billingCount: 1, + billingFrequency: "Month", + durationCount: 1, + duration: "Month", + }, + { + amount: "10.00", + billingCount: 1, + billingFrequency: "Month", + durationCount: 1, + duration: "UntilCancelled", + }, + ], + taxInclusive: false, + }, + { + pricingStrategyId: 1168, + priceCode: "H7SCJB", + name: "premium all access", + description: "

Get access to premium content for a low rate

", + gift: false, + summary: null, + currencyCode: "USD", + currencyDisplayFormat: "symbol", + currencyLocale: "en-US", + rates: [ + { + amount: "10.00", + billingCount: 1, + billingFrequency: "Month", + durationCount: 1, + duration: "UntilCancelled", + }, + ], + taxInclusive: false, + }, + ], + defaultSwgProduct: false, + }, + ], + attributes: [], + default: true, +}; + +global.fetch = jest.fn(() => + Promise.resolve({ + json: () => Promise.resolve(testOfferResponse), + }), +); + +jest.mock("fusion:properties", () => { + const retailOrigin = "https://corecomponents-arc-demo-3-prod.api.cdn.arcpublishing.com"; + const retailEndpoint = "/retail/public/v1/offer/live/"; + return jest.fn(() => ({ + __esModule: true, + api: { + retail: { + origin: retailOrigin, + endpoint: retailEndpoint, + }, + }, + })); +}); + +describe("useOffer", () => { + beforeEach(() => { + jest.resetModules(); + jest.clearAllMocks(); + }); + + it("useOffer hook runs correctly", async () => { + const { result } = renderHook(() => useOffer({ campaignCode: "default" })); + + expect(result.current.error).toBe(null); + expect(result.current.offer).toBe(null); + expect(result.current.isFetching).toBe(true); + + await waitFor(() => expect(result.current.error).toEqual(null)); + await waitFor(() => expect(result.current.offer).toEqual(testOfferResponse)); + await waitFor(() => expect(result.current.isFetching).toEqual(false)); + }); + + it("use offer uses fallback default string", async () => { + const { result } = renderHook(() => useOffer({ campaignCode: "" })); + + expect(result.current.error).toBe(null); + expect(result.current.offer).toBe(null); + expect(result.current.isFetching).toBe(true); + + await waitFor(() => expect(result.current.error).toEqual(null)); + await waitFor(() => expect(result.current.offer).toEqual(testOfferResponse)); + await waitFor(() => expect(result.current.isFetching).toEqual(false)); + }); + + it("useOffer hook handles an error state during fetch", async () => { + global.fetch = jest.fn().mockRejectedValue(new Error("fetch error")); + + const { result } = renderHook(() => useOffer({ campaignCode: "default" })); + + expect(result.current.error).toBe(null); + expect(result.current.offer).toBe(null); + expect(result.current.isFetching).toBe(true); + + await waitFor(() => + expect(result.current.error).toEqual("Error in fetching retail offers: Error: fetch error"), + ); + await waitFor(() => expect(result.current.offer).toEqual(null)); + await waitFor(() => expect(result.current.isFetching).toEqual(false)); + }); + + it("useOffer hook handles an error state during fetch", async () => { + global.fetch = jest.fn().mockRejectedValue(new Error("fetch error")); + + const { result } = renderHook(() => useOffer({ campaignCode: "default" })); + + expect(result.current.error).toBe(null); + expect(result.current.offer).toBe(null); + expect(result.current.isFetching).toBe(true); + + await waitFor(() => + expect(result.current.error).toEqual("Error in fetching retail offers: Error: fetch error"), + ); + await waitFor(() => expect(result.current.offer).toEqual(null)); + await waitFor(() => expect(result.current.isFetching).toEqual(false)); + }); +}); + diff --git a/blocks/subscriptions-block/components/useOrder.jsx b/blocks/subscriptions-block/components/useOrder.jsx index 0859413a5e..766bf8913e 100644 --- a/blocks/subscriptions-block/components/useOrder.jsx +++ b/blocks/subscriptions-block/components/useOrder.jsx @@ -23,7 +23,7 @@ const useOrder = (orderNumber) => { const getCart = async () => { try { const currentCart = await Sales.getCart(); - if(currentCart.items.length){ + if(currentCart?.items?.length){ setCart(currentCart); } } catch (e) { diff --git a/blocks/subscriptions-block/components/useOrder.test.jsx b/blocks/subscriptions-block/components/useOrder.test.jsx index cdb89f1695..1290d634a5 100644 --- a/blocks/subscriptions-block/components/useOrder.test.jsx +++ b/blocks/subscriptions-block/components/useOrder.test.jsx @@ -1,5 +1,3 @@ -import React from "react"; - import { useSales } from "@wpmedia/arc-themes-components"; import { waitFor, renderHook } from "@testing-library/react"; @@ -297,9 +295,9 @@ describe("The OfferToProductList component", () => { isInitialized: true, Sales: { getCart: jest.fn(() => Promise.resolve(emptyCart)), - getOrderDetails: jest.fn(() => { - return Promise.resolve(orderDetail); - }), + getOrderDetails: jest.fn(() => + Promise.resolve(orderDetail) + ), }, }); @@ -332,7 +330,7 @@ describe("The OfferToProductList component", () => { ], }, ], - } + }; const { result } = renderHook(() => useOrder("6OGJP2GGVJ7GT8L3")); @@ -346,13 +344,12 @@ describe("The OfferToProductList component", () => { }); it("getOrderDetail is returning an error", async () => { - - const error = {code: '200019', message: 'Access Denied'}; + const error = { code: "200019", message: "Access Denied" }; useSales.mockReturnValueOnce({ isInitialized: true, Sales: { getCart: jest.fn(() => Promise.resolve(emptyCart)), - getOrderDetails: jest.fn().mockRejectedValueOnce(error) + getOrderDetails: jest.fn().mockRejectedValueOnce(error), }, }); @@ -365,5 +362,26 @@ describe("The OfferToProductList component", () => { await waitFor(() => expect(result.current.cartDetails).toEqual(undefined)); await waitFor(() => expect(result.current.orderDetails).toEqual(undefined)); await waitFor(() => expect(result.current.error).toEqual(error)); - }) + }); + + it("getCart is returning an error", async () => { + const error = { code: "0", message: "Unexpected Server error" }; + useSales.mockReturnValueOnce({ + isInitialized: true, + Sales: { + getCart: jest.fn().mockRejectedValueOnce(error), + getOrderDetails: jest.fn(() => {}), + }, + }); + + const { result } = renderHook(() => useOrder()); + + expect(result.current.cartDetails).toBe(undefined); + expect(result.current.orderDetails).toBe(undefined); + expect(result.current.error).toBe(undefined); + + await waitFor(() => expect(result.current.cartDetails).toEqual(undefined)); + await waitFor(() => expect(result.current.orderDetails).toEqual(undefined)); + await waitFor(() => expect(result.current.error).toEqual(error)); + }); }); From 9188b5770d771065f2de7d60ab1f013b4aafeb0d Mon Sep 17 00:00:00 2001 From: LauraPinilla Date: Mon, 1 Apr 2024 01:33:29 -0600 Subject: [PATCH 6/6] linting errors --- .../components/OfferCard/index.test.jsx | 15 ++++++++------- .../components/OrderInformation/index.test.jsx | 4 ++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/blocks/subscriptions-block/components/OfferCard/index.test.jsx b/blocks/subscriptions-block/components/OfferCard/index.test.jsx index 5c04768816..2f5ac46676 100644 --- a/blocks/subscriptions-block/components/OfferCard/index.test.jsx +++ b/blocks/subscriptions-block/components/OfferCard/index.test.jsx @@ -25,20 +25,21 @@ describe("OfferCard", () => { expect(screen.getByRole("button")).not.toBeNull(); const ul = screen.getByRole("list"); - expect(ul.childElementCount).toBe(2); + expect(ul.childElementCount).toBe(2); // eslint-disable-line - const ulByClass = document.getElementsByClassName("test-block__card--features--feature-item"); + const ulByClass = document.getElementsByClassName("test-block__card--features--feature-item"); // eslint-disable-line expect(ulByClass.length).toBe(2); - expect(screen.getByText(props.features[0].featureText)).not.toBeNull(); - expect(screen.getByText(props.features[1].featureText)).not.toBeNull(); + expect(screen.getByText(props.features[0].featureText)).not.toBeNull(); // eslint-disable-line + expect(screen.getByText(props.features[1].featureText)).not.toBeNull(); // eslint-disable-line }); it("does not render headline if not present", () => { render( , ); - const headingElement = document.getElementsByClassName('.b-offer__card h1'); + + const headingElement = document.getElementsByClassName('.b-offer__card h1'); // eslint-disable-line expect(headingElement.length).toBe(0) }); @@ -47,7 +48,7 @@ describe("OfferCard", () => { , ); - const headingElement = document.getElementsByClassName('.b-offer__card p'); + const headingElement = document.getElementsByClassName('.b-offer__card p'); // eslint-disable-line expect(headingElement.length).toBe(0) }); @@ -75,7 +76,7 @@ describe("OfferCard", () => { it("does not render features", () => { render(); - const ulByClass = document.getElementsByClassName("test-block__card--features--feature-item"); + const ulByClass = document.getElementsByClassName("test-block__card--features--feature-item"); // eslint-disable-line expect(ulByClass.length).toBe(0); }); }); diff --git a/blocks/subscriptions-block/components/OrderInformation/index.test.jsx b/blocks/subscriptions-block/components/OrderInformation/index.test.jsx index 270cb3d827..3246b92ef0 100644 --- a/blocks/subscriptions-block/components/OrderInformation/index.test.jsx +++ b/blocks/subscriptions-block/components/OrderInformation/index.test.jsx @@ -99,7 +99,7 @@ describe('Order Information component', () => { ).toBeVisible(); expect(screen.getByText("checkout-block.due-today")).toBeVisible(); - const headingElement = document.getElementsByClassName('c-heading'); + const headingElement = document.getElementsByClassName('c-heading'); // eslint-disable-line expect(headingElement.length).toBe(2) }); @@ -168,7 +168,7 @@ describe('Order Information component', () => { showProductFeatures={showProductFeat} />, ); - const headingElement = document.getElementsByClassName('c-heading'); + const headingElement = document.getElementsByClassName('c-heading'); // eslint-disable-line expect(headingElement.length).toBe(1) }) });