diff --git a/packages/template-retail-react-app/app/components/_app/index.jsx b/packages/template-retail-react-app/app/components/_app/index.jsx index 429dc7c24c..4046ede86f 100644 --- a/packages/template-retail-react-app/app/components/_app/index.jsx +++ b/packages/template-retail-react-app/app/components/_app/index.jsx @@ -57,7 +57,7 @@ import {useCurrentBasket} from '@salesforce/retail-react-app/app/hooks/use-curre import {withCommerceSdkReact} from '@salesforce/retail-react-app/app/components/with-commerce-sdk-react/with-commerce-sdk-react' // Localization -import {FormattedNumber, IntlProvider} from 'react-intl' +import {IntlProvider} from 'react-intl' // Others import {watchOnlineStatus, flatten, isServer} from '@salesforce/retail-react-app/app/utils/utils' diff --git a/packages/template-retail-react-app/app/components/display-price/index.jsx b/packages/template-retail-react-app/app/components/display-price/index.jsx index f94b3c856c..960ff004e7 100644 --- a/packages/template-retail-react-app/app/components/display-price/index.jsx +++ b/packages/template-retail-react-app/app/components/display-price/index.jsx @@ -66,7 +66,7 @@ const DisplayPrice = ({priceData, currency}) => { other {} } {isASet, select, - true {From {salePrice}} + true {From {salePrice}} false { { diff --git a/packages/template-retail-react-app/app/components/display-price/index.test.js b/packages/template-retail-react-app/app/components/display-price/index.test.js index 336591a695..08c45711bc 100644 --- a/packages/template-retail-react-app/app/components/display-price/index.test.js +++ b/packages/template-retail-react-app/app/components/display-price/index.test.js @@ -29,9 +29,10 @@ describe('DisplayPrice', function () { const {container} = renderWithProviders() const currentPriceTag = container.querySelectorAll('b') const strikethroughPriceTag = container.querySelectorAll('s') - expect(within(currentPriceTag[0]).getByText(/£90\.00/i)).toBeDefined() + // From and salePrice are in two separate b tags + expect(within(currentPriceTag[1]).getByText(/£90\.00/i)).toBeDefined() expect(within(strikethroughPriceTag[0]).getByText(/£100\.00/i)).toBeDefined() - expect(currentPriceTag).toHaveLength(1) + expect(currentPriceTag).toHaveLength(2) expect(strikethroughPriceTag).toHaveLength(1) }) diff --git a/packages/template-retail-react-app/app/components/product-tile/index.jsx b/packages/template-retail-react-app/app/components/product-tile/index.jsx index 64c168c4b4..953b38e97c 100644 --- a/packages/template-retail-react-app/app/components/product-tile/index.jsx +++ b/packages/template-retail-react-app/app/components/product-tile/index.jsx @@ -5,7 +5,7 @@ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import React, {useMemo, useRef} from 'react' +import React, {useRef} from 'react' import PropTypes from 'prop-types' import {HeartIcon, HeartSolidIcon} from '@salesforce/retail-react-app/app/components/icons' import DisplayPrice from '@salesforce/retail-react-app/app/components/display-price' @@ -26,12 +26,11 @@ import DynamicImage from '@salesforce/retail-react-app/app/components/dynamic-im import {useIntl} from 'react-intl' // Other -import {productUrlBuilder, rebuildPathWithParams} from '@salesforce/retail-react-app/app/utils/url' +import {productUrlBuilder} from '@salesforce/retail-react-app/app/utils/url' import Link from '@salesforce/retail-react-app/app/components/link' import withRegistration from '@salesforce/retail-react-app/app/components/with-registration' import {getPriceData} from '@salesforce/retail-react-app/app/utils/product-utils' import {useCurrency} from '@salesforce/retail-react-app/app/hooks' -import {PRICE_DISPLAY_FORMAT} from '@salesforce/retail-react-app/app/constants' const IconButtonWithRegistration = withRegistration(IconButton) diff --git a/packages/template-retail-react-app/app/components/product-tile/index.test.js b/packages/template-retail-react-app/app/components/product-tile/index.test.js index 0b4a1df8b4..6398f2973b 100644 --- a/packages/template-retail-react-app/app/components/product-tile/index.test.js +++ b/packages/template-retail-react-app/app/components/product-tile/index.test.js @@ -9,6 +9,7 @@ import ProductTile, {Skeleton} from '@salesforce/retail-react-app/app/components import {renderWithProviders} from '@salesforce/retail-react-app/app/utils/test-utils' import {fireEvent, within} from '@testing-library/react' import { + mockMasterProductHitWithMultipleVariants, mockMasterProductHitWithOneVariant, mockProductSearchItem, mockProductSetHit, @@ -50,29 +51,30 @@ test('Remove from wishlist cannot be muti-clicked', () => { expect(onClick).toHaveBeenCalledTimes(1) }) -test('renders exact price with strikethrough price for master product can be filtered down to one variant ', () => { - const {getByText, container} = renderWithProviders( - +test('renders exact price with strikethrough price for master product can be has various variants', () => { + const {queryByText, getByText, container} = renderWithProviders( + ) - expect(getByText(/black flat front wool suit/i)).toBeInTheDocument() - expect(getByText(/£191\.99/i)).toBeInTheDocument() - expect(getByText(/£320\.00/i)).toBeInTheDocument() + expect(getByText(/Black Single Pleat Athletic Fit Wool Suit - Edit/i)).toBeInTheDocument() + expect(queryByText(/from/i)).toBeInTheDocument() const salePriceTag = container.querySelectorAll('b') const strikethroughPriceTag = container.querySelectorAll('s') - expect(within(salePriceTag[0]).getByText(/£191\.99/i)).toBeDefined() - expect(within(strikethroughPriceTag[0]).getByText(/£320\.00/i)).toBeDefined() - expect(salePriceTag).toHaveLength(1) + expect(within(salePriceTag[1]).getByText(/£191\.99/i)).toBeDefined() + expect(within(strikethroughPriceTag[0]).getByText(/£223\.99/i)).toBeDefined() + // From and price are in separate b tag + expect(salePriceTag).toHaveLength(2) expect(strikethroughPriceTag).toHaveLength(1) }) -test('renders exact price with strikethrough price for master product can be filtered down to one variant ', () => { - const {getByText, container} = renderWithProviders( +test('renders exact price with strikethrough price for master product can be filtered down to one variant', () => { + const {getByText, queryByText, container} = renderWithProviders( ) expect(getByText(/black flat front wool suit/i)).toBeInTheDocument() expect(getByText(/£191\.99/i)).toBeInTheDocument() expect(getByText(/£320\.00/i)).toBeInTheDocument() + expect(queryByText(/from/i)).not.toBeInTheDocument() const salePriceTag = container.querySelectorAll('b') const strikethroughPriceTag = container.querySelectorAll('s') @@ -82,13 +84,13 @@ test('renders exact price with strikethrough price for master product can be fil expect(strikethroughPriceTag).toHaveLength(1) }) -test('Product set - does not render strike through price', () => { - const {getByText, queryByText, container} = renderWithProviders( +test('Product set - shows range From X where X is the lowest price child', () => { + const {getByText, queryByText} = renderWithProviders( ) expect(getByText(/Winter Look/i)).toBeInTheDocument() - expect(queryByText(/from/i)).not.toBeInTheDocument() - expect(queryByText(/£40\.16/i)).not.toBeInTheDocument() + expect(queryByText(/from/i)).toBeInTheDocument() + expect(queryByText(/£40\.16/i)).toBeInTheDocument() expect(queryByText(/£44\.16/i)).not.toBeInTheDocument() }) diff --git a/packages/template-retail-react-app/app/components/product-view/index.jsx b/packages/template-retail-react-app/app/components/product-view/index.jsx index bb27225950..e12107f837 100644 --- a/packages/template-retail-react-app/app/components/product-view/index.jsx +++ b/packages/template-retail-react-app/app/components/product-view/index.jsx @@ -63,7 +63,8 @@ const ProductViewHeader = ({name, currency, priceData, category}) => { ProductViewHeader.propTypes = { name: PropTypes.string, currency: PropTypes.string, - category: PropTypes.array + category: PropTypes.array, + priceData: PropTypes.object } const ButtonWithRegistration = withRegistration(Button) diff --git a/packages/template-retail-react-app/app/components/product-view/index.test.js b/packages/template-retail-react-app/app/components/product-view/index.test.js index f9ab13682f..e13c583c94 100644 --- a/packages/template-retail-react-app/app/components/product-view/index.test.js +++ b/packages/template-retail-react-app/app/components/product-view/index.test.js @@ -174,8 +174,9 @@ test('renders a product set properly - child item', () => { expect(variationAttributes).toHaveLength(2) expect(quantityPicker).toBeInTheDocument() - // What should _not_ exist: - expect(fromLabels).toHaveLength(0) + // since setProducts are master products, as pricing now display From X (cross) Y where X Y are sale and lis price respectively + // of the variant that has lowest price (including promotional price) + expect(fromLabels).toHaveLength(2) }) test('validateOrderability callback is called when adding a set to cart', async () => { diff --git a/packages/template-retail-react-app/app/mocks/product-search-hit-data.js b/packages/template-retail-react-app/app/mocks/product-search-hit-data.js index 6984cb7437..9970e08998 100644 --- a/packages/template-retail-react-app/app/mocks/product-search-hit-data.js +++ b/packages/template-retail-react-app/app/mocks/product-search-hit-data.js @@ -1,3 +1,10 @@ +/* + * Copyright (c) 2024, salesforce.com, inc. + * All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + const mockProductSearchItem = { currency: 'USD', image: { diff --git a/packages/template-retail-react-app/app/static/translations/compiled/en-GB.json b/packages/template-retail-react-app/app/static/translations/compiled/en-GB.json index 6b9d746054..77e69087e3 100644 --- a/packages/template-retail-react-app/app/static/translations/compiled/en-GB.json +++ b/packages/template-retail-react-app/app/static/translations/compiled/en-GB.json @@ -2815,8 +2815,14 @@ { "children": [ { - "type": 1, - "value": "salePrice" + "children": [ + { + "type": 1, + "value": "salePrice" + } + ], + "type": 8, + "value": "price" } ], "type": 8, diff --git a/packages/template-retail-react-app/app/static/translations/compiled/en-US.json b/packages/template-retail-react-app/app/static/translations/compiled/en-US.json index 6b9d746054..77e69087e3 100644 --- a/packages/template-retail-react-app/app/static/translations/compiled/en-US.json +++ b/packages/template-retail-react-app/app/static/translations/compiled/en-US.json @@ -2815,8 +2815,14 @@ { "children": [ { - "type": 1, - "value": "salePrice" + "children": [ + { + "type": 1, + "value": "salePrice" + } + ], + "type": 8, + "value": "price" } ], "type": 8, diff --git a/packages/template-retail-react-app/app/static/translations/compiled/en-XA.json b/packages/template-retail-react-app/app/static/translations/compiled/en-XA.json index bd99089899..382f633a41 100644 --- a/packages/template-retail-react-app/app/static/translations/compiled/en-XA.json +++ b/packages/template-retail-react-app/app/static/translations/compiled/en-XA.json @@ -5627,8 +5627,14 @@ { "children": [ { - "type": 1, - "value": "salePrice" + "children": [ + { + "type": 1, + "value": "salePrice" + } + ], + "type": 8, + "value": "price" } ], "type": 8, diff --git a/packages/template-retail-react-app/app/utils/test-utils.js b/packages/template-retail-react-app/app/utils/test-utils.js index d9e9cabfd6..ba4c9ccf33 100644 --- a/packages/template-retail-react-app/app/utils/test-utils.js +++ b/packages/template-retail-react-app/app/utils/test-utils.js @@ -26,7 +26,6 @@ import {createUrlTemplate} from '@salesforce/retail-react-app/app/utils/url' import {getSiteByReference} from '@salesforce/retail-react-app/app/utils/site-utils' import jwt from 'jsonwebtoken' import userEvent from '@testing-library/user-event' -import {Text} from '@chakra-ui/react' // This JWT's payload is special // it includes 3 fields that commerce-sdk-react cares: // exp, isb and sub diff --git a/packages/template-retail-react-app/app/utils/utils.test.js b/packages/template-retail-react-app/app/utils/utils.test.js index 76840ff6d0..f9388477b7 100644 --- a/packages/template-retail-react-app/app/utils/utils.test.js +++ b/packages/template-retail-react-app/app/utils/utils.test.js @@ -198,7 +198,7 @@ describe('getSmallestValByKey', function () { } ] const val = getSmallestValByProperty(data, 'price') - expect(val).toEqual(9) + expect(val).toBe(9) }) test('should undefined if array is not passed in', () => { const data = { diff --git a/packages/template-retail-react-app/translations/en-GB.json b/packages/template-retail-react-app/translations/en-GB.json index 74ed825ced..42282a53a8 100644 --- a/packages/template-retail-react-app/translations/en-GB.json +++ b/packages/template-retail-react-app/translations/en-GB.json @@ -1067,7 +1067,7 @@ "defaultMessage": "Remove {product} from wishlist" }, "product_tile.price_display": { - "defaultMessage": "{isMaster, select, true { { isRange, select, true { { isOnSale, select, true {From} fales { { hasRepresentedProduct, select, true {From} false {From} other {} } } other {From} } } false {} other {} } } false {} other {} } {isASet, select, true {From {salePrice}} false { { isOnSale, select, true { {salePrice} {listPrice} } false { { hasRepresentedProduct, select, true {{salePrice}} false {{salePrice}} other {{salePrice}} } } other {{salePrice}} } } other {} }" + "defaultMessage": "{isMaster, select, true { { isRange, select, true { { isOnSale, select, true {From} fales { { hasRepresentedProduct, select, true {From} false {From} other {} } } other {From} } } false {} other {} } } false {} other {} } {isASet, select, true {From {salePrice}} false { { isOnSale, select, true { {salePrice} {listPrice} } false { { hasRepresentedProduct, select, true {{salePrice}} false {{salePrice}} other {{salePrice}} } } other {{salePrice}} } } other {} }" }, "product_view.button.add_set_to_cart": { "defaultMessage": "Add Set to Cart" diff --git a/packages/template-retail-react-app/translations/en-US.json b/packages/template-retail-react-app/translations/en-US.json index 74ed825ced..42282a53a8 100644 --- a/packages/template-retail-react-app/translations/en-US.json +++ b/packages/template-retail-react-app/translations/en-US.json @@ -1067,7 +1067,7 @@ "defaultMessage": "Remove {product} from wishlist" }, "product_tile.price_display": { - "defaultMessage": "{isMaster, select, true { { isRange, select, true { { isOnSale, select, true {From} fales { { hasRepresentedProduct, select, true {From} false {From} other {} } } other {From} } } false {} other {} } } false {} other {} } {isASet, select, true {From {salePrice}} false { { isOnSale, select, true { {salePrice} {listPrice} } false { { hasRepresentedProduct, select, true {{salePrice}} false {{salePrice}} other {{salePrice}} } } other {{salePrice}} } } other {} }" + "defaultMessage": "{isMaster, select, true { { isRange, select, true { { isOnSale, select, true {From} fales { { hasRepresentedProduct, select, true {From} false {From} other {} } } other {From} } } false {} other {} } } false {} other {} } {isASet, select, true {From {salePrice}} false { { isOnSale, select, true { {salePrice} {listPrice} } false { { hasRepresentedProduct, select, true {{salePrice}} false {{salePrice}} other {{salePrice}} } } other {{salePrice}} } } other {} }" }, "product_view.button.add_set_to_cart": { "defaultMessage": "Add Set to Cart"