From 5a9a04adddbc902296529640b2ac4edf5174b722 Mon Sep 17 00:00:00 2001 From: Alex Vuong Date: Wed, 15 May 2024 16:06:49 -0700 Subject: [PATCH 1/3] separate pricing components into its own files --- .../display-price/current-price.jsx | 61 +++++++++ .../app/components/display-price/index.jsx | 126 +----------------- .../components/display-price/index.test.js | 36 ++++- .../components/display-price/list-price.jsx | 62 +++++++++ .../app/components/display-price/messages.js | 28 ++++ 5 files changed, 189 insertions(+), 124 deletions(-) create mode 100644 packages/template-retail-react-app/app/components/display-price/current-price.jsx create mode 100644 packages/template-retail-react-app/app/components/display-price/list-price.jsx create mode 100644 packages/template-retail-react-app/app/components/display-price/messages.js diff --git a/packages/template-retail-react-app/app/components/display-price/current-price.jsx b/packages/template-retail-react-app/app/components/display-price/current-price.jsx new file mode 100644 index 0000000000..31127c5fd8 --- /dev/null +++ b/packages/template-retail-react-app/app/components/display-price/current-price.jsx @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2024, Salesforce, 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 + */ + +import React from 'react' +import PropTypes from 'prop-types' +import {Text} from '@salesforce/retail-react-app/app/components/shared/ui' +import {useIntl} from 'react-intl' +import msg from './messages' + +/** + * Component that displays current price of a product with a11y + * @param price - price of the product + * @param as - an HTML tag or component to be rendered as + * @param isRange - show price as range or not + * @param currency - currency to show the price in + * @param extraProps - extra props to be passed into Text Component + * @returns {JSX.Element} + */ +const CurrentPrice = ({price, as, isRange = false, currency, ...extraProps}) => { + const intl = useIntl() + const currentPriceText = intl.formatNumber(price, { + style: 'currency', + currency + }) + return isRange ? ( + + {intl.formatMessage(msg.currentPriceWithRange, { + currentPrice: currentPriceText + })} + + ) : ( + + {currentPriceText} + + ) +} +CurrentPrice.propTypes = { + price: PropTypes.number.isRequired, + currency: PropTypes.string.isRequired, + as: PropTypes.string, + isRange: PropTypes.bool, + extraProps: PropTypes.object +} + +export default CurrentPrice 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 b9d7c76bc0..39fe65b3c4 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 @@ -7,33 +7,9 @@ import React from 'react' import PropTypes from 'prop-types' -import {Box, Text} from '@salesforce/retail-react-app/app/components/shared/ui' -import {defineMessages, useIntl} from 'react-intl' - -const msg = defineMessages({ - // price display - currentPriceWithRange: { - id: 'display_price.label.current_price_with_range', - defaultMessage: 'From {currentPrice}' - }, - // aria-label - ariaLabelCurrentPrice: { - id: 'display_price.assistive_msg.current_price', - defaultMessage: `current price {currentPrice}` - }, - ariaLabelCurrentPriceWithRange: { - id: 'display_price.assistive_msg.current_price_with_range', - defaultMessage: `From current price {currentPrice}` - }, - ariaLabelListPrice: { - id: 'display_price.assistive_msg.strikethrough_price', - defaultMessage: `original price {listPrice}` - }, - ariaLabelListPriceWithRange: { - id: 'display_price.assistive_msg.strikethrough_price_with_range', - defaultMessage: `From original price {listPrice}` - } -}) +import {Box} from '@salesforce/retail-react-app/app/components/shared/ui' +import CurrentPrice from '@salesforce/retail-react-app/app/components/display-price/current-price' +import ListPrice from '@salesforce/retail-react-app/app/components/display-price/list-price' /** * @param priceData - price info extracted from a product @@ -73,101 +49,6 @@ const DisplayPrice = ({priceData, currency}) => { return {renderPriceSet(false)} } -/** - * Component that displays current price of a product with a11y - * @param price - price of the product - * @param as - an HTML tag or component to be rendered as - * @param isRange - show price as range or not - * @param currency - currency to show the price in - * @param extraProps - extra props to be passed into Text Component - * @returns {JSX.Element} - */ -const CurrentPrice = ({price, as, isRange = false, currency, ...extraProps}) => { - const intl = useIntl() - const currentPriceText = intl.formatNumber(price, { - style: 'currency', - currency - }) - return isRange ? ( - - {intl.formatMessage(msg.currentPriceWithRange, { - currentPrice: currentPriceText - })} - - ) : ( - - {currentPriceText} - - ) -} -CurrentPrice.propTypes = { - price: PropTypes.number.isRequired, - currency: PropTypes.string.isRequired, - as: PropTypes.string, - isRange: PropTypes.bool, - extraProps: PropTypes.object -} - -/** - * Component that displays list price of a product with a11y - * @param price - price of the product - * @param as - an HTML tag or component to be rendered as - * @param isRange - show price as range or not - * @param props - extra props to be passed into Text Component - * @param extraProps - extra props to be passed into Text Component - * @returns {JSX.Element} - */ -const ListPrice = ({price, isRange = false, as = 's', currency, ...extraProps}) => { - const intl = useIntl() - const listPriceText = intl.formatNumber(price, { - style: 'currency', - currency - }) - - return isRange ? ( - - {listPriceText} - - ) : ( - - {listPriceText} - - ) -} - -ListPrice.propTypes = { - price: PropTypes.number.isRequired, - currency: PropTypes.string.isRequired, - as: PropTypes.string, - isRange: PropTypes.bool, - extraProps: PropTypes.object -} DisplayPrice.propTypes = { priceData: PropTypes.shape({ @@ -184,4 +65,3 @@ DisplayPrice.propTypes = { } export default DisplayPrice -export {ListPrice, CurrentPrice, msg} 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 37a6ce62d5..987e7417af 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 @@ -8,6 +8,8 @@ import React from 'react' import {screen, within} from '@testing-library/react' import DisplayPrice from '@salesforce/retail-react-app/app/components/display-price/index' import {renderWithProviders} from '@salesforce/retail-react-app/app/utils/test-utils' +import CurrentPrice from '@salesforce/retail-react-app/app/components/display-price/current-price' +import ListPrice from '@salesforce/retail-react-app/app/components/display-price/list-price' describe('DisplayPrice', function () { const data = { @@ -28,7 +30,6 @@ describe('DisplayPrice', function () { const {container} = renderWithProviders() const currentPriceTag = container.querySelectorAll('b') const strikethroughPriceTag = container.querySelectorAll('s') - // From and salePrice are in two separate b tags expect(within(currentPriceTag[0]).getByText(/£90\.00/i)).toBeDefined() expect(within(strikethroughPriceTag[0]).getByText(/£100\.00/i)).toBeDefined() expect(currentPriceTag).toHaveLength(1) @@ -46,3 +47,36 @@ describe('DisplayPrice', function () { expect(screen.getByText(/£100\.00/i)).toBeInTheDocument() }) }) + +describe('CurrentPrice', function () { + test('should render exact price with correct aria-label', () => { + const {container} = renderWithProviders() + expect(screen.getByText(/£100\.00/i)).toBeInTheDocument() + expect(screen.getByLabelText(/current price £100\.00/i)).toBeInTheDocument() + }) + + test('should render range price', () => { + renderWithProviders() + expect(screen.getByText(/£100\.00/i)).toBeInTheDocument() + expect(screen.getByText(/from/i)).toBeInTheDocument() + expect(screen.getByLabelText(/from current price £100\.00/i)).toBeInTheDocument() + }) +}) + +describe('ListPrice', function () { + test('should render strikethrough price with exact price in aria-label', () => { + const {container} = renderWithProviders() + const strikethroughPriceTag = container.querySelectorAll('s') + expect(screen.getByLabelText(/original price £100\.00/i)).toBeInTheDocument() + expect(within(strikethroughPriceTag[0]).getByText(/£100\.00/i)).toBeDefined() + }) + + test('should render strikethrough price with range price in aria-label', () => { + const {container} = renderWithProviders( + + ) + const strikethroughPriceTag = container.querySelectorAll('s') + expect(screen.getByLabelText(/from original price £100\.00/i)).toBeInTheDocument() + expect(within(strikethroughPriceTag[0]).getByText(/£100\.00/i)).toBeDefined() + }) +}) diff --git a/packages/template-retail-react-app/app/components/display-price/list-price.jsx b/packages/template-retail-react-app/app/components/display-price/list-price.jsx new file mode 100644 index 0000000000..893d7db1d8 --- /dev/null +++ b/packages/template-retail-react-app/app/components/display-price/list-price.jsx @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2023, Salesforce, 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 + */ + +import React from 'react' +import PropTypes from 'prop-types' +import {Text} from '@salesforce/retail-react-app/app/components/shared/ui' +import {useIntl} from 'react-intl' +import msg from './messages' +/** + * Component that displays list price of a product with a11y + * @param price - price of the product + * @param as - an HTML tag or component to be rendered as + * @param isRange - show price as range or not + * @param props - extra props to be passed into Text Component + * @param extraProps - extra props to be passed into Text Component + * @returns {JSX.Element} + */ +const ListPrice = ({price, isRange = false, as = 's', currency, ...extraProps}) => { + const intl = useIntl() + const listPriceText = intl.formatNumber(price, { + style: 'currency', + currency + }) + + return isRange ? ( + + {listPriceText} + + ) : ( + + {listPriceText} + + ) +} + +ListPrice.propTypes = { + price: PropTypes.number.isRequired, + currency: PropTypes.string.isRequired, + as: PropTypes.string, + isRange: PropTypes.bool, + extraProps: PropTypes.object +} + +export default ListPrice diff --git a/packages/template-retail-react-app/app/components/display-price/messages.js b/packages/template-retail-react-app/app/components/display-price/messages.js new file mode 100644 index 0000000000..a3700f74f0 --- /dev/null +++ b/packages/template-retail-react-app/app/components/display-price/messages.js @@ -0,0 +1,28 @@ +import {defineMessages} from 'react-intl' + +const messages = defineMessages({ + // price display + currentPriceWithRange: { + id: 'display_price.label.current_price_with_range', + defaultMessage: 'From {currentPrice}' + }, + // aria-label + ariaLabelCurrentPrice: { + id: 'display_price.assistive_msg.current_price', + defaultMessage: `current price {currentPrice}` + }, + ariaLabelCurrentPriceWithRange: { + id: 'display_price.assistive_msg.current_price_with_range', + defaultMessage: `From current price {currentPrice}` + }, + ariaLabelListPrice: { + id: 'display_price.assistive_msg.strikethrough_price', + defaultMessage: `original price {listPrice}` + }, + ariaLabelListPriceWithRange: { + id: 'display_price.assistive_msg.strikethrough_price_with_range', + defaultMessage: `From original price {listPrice}` + } +}) + +export default messages From e43dccbd0a92770ef09abfb329fad9c760573f35 Mon Sep 17 00:00:00 2001 From: Alex Vuong Date: Wed, 15 May 2024 16:28:21 -0700 Subject: [PATCH 2/3] lint --- .../app/components/display-price/current-price.jsx | 2 +- .../app/components/display-price/index.test.js | 2 +- .../app/components/display-price/list-price.jsx | 4 ++-- .../app/components/display-price/messages.js | 7 +++++++ 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/template-retail-react-app/app/components/display-price/current-price.jsx b/packages/template-retail-react-app/app/components/display-price/current-price.jsx index 31127c5fd8..2db3db73d3 100644 --- a/packages/template-retail-react-app/app/components/display-price/current-price.jsx +++ b/packages/template-retail-react-app/app/components/display-price/current-price.jsx @@ -9,7 +9,7 @@ import React from 'react' import PropTypes from 'prop-types' import {Text} from '@salesforce/retail-react-app/app/components/shared/ui' import {useIntl} from 'react-intl' -import msg from './messages' +import msg from '@salesforce/retail-react-app/app/components/display-price/messages' /** * Component that displays current price of a product with a11y 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 987e7417af..d0a6fe057f 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 @@ -50,7 +50,7 @@ describe('DisplayPrice', function () { describe('CurrentPrice', function () { test('should render exact price with correct aria-label', () => { - const {container} = renderWithProviders() + renderWithProviders() expect(screen.getByText(/£100\.00/i)).toBeInTheDocument() expect(screen.getByLabelText(/current price £100\.00/i)).toBeInTheDocument() }) diff --git a/packages/template-retail-react-app/app/components/display-price/list-price.jsx b/packages/template-retail-react-app/app/components/display-price/list-price.jsx index 893d7db1d8..8aa443d69b 100644 --- a/packages/template-retail-react-app/app/components/display-price/list-price.jsx +++ b/packages/template-retail-react-app/app/components/display-price/list-price.jsx @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Salesforce, Inc. + * Copyright (c) 2024, Salesforce, 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 @@ -9,7 +9,7 @@ import React from 'react' import PropTypes from 'prop-types' import {Text} from '@salesforce/retail-react-app/app/components/shared/ui' import {useIntl} from 'react-intl' -import msg from './messages' +import msg from '@salesforce/retail-react-app/app/components/display-price/messages' /** * Component that displays list price of a product with a11y * @param price - price of the product diff --git a/packages/template-retail-react-app/app/components/display-price/messages.js b/packages/template-retail-react-app/app/components/display-price/messages.js index a3700f74f0..8b9ae9e038 100644 --- a/packages/template-retail-react-app/app/components/display-price/messages.js +++ b/packages/template-retail-react-app/app/components/display-price/messages.js @@ -1,3 +1,10 @@ +/* + * Copyright (c) 2023, Salesforce, 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 + */ + import {defineMessages} from 'react-intl' const messages = defineMessages({ From f872153dec0c4bff5f4e194b11a54f31e2cea8fb Mon Sep 17 00:00:00 2001 From: Alex Vuong Date: Wed, 15 May 2024 17:44:24 -0700 Subject: [PATCH 3/3] fix flaky test --- .../index.test.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/template-retail-react-app/app/components/unavailable-product-confirmation-modal/index.test.js b/packages/template-retail-react-app/app/components/unavailable-product-confirmation-modal/index.test.js index 3488211698..206073056a 100644 --- a/packages/template-retail-react-app/app/components/unavailable-product-confirmation-modal/index.test.js +++ b/packages/template-retail-react-app/app/components/unavailable-product-confirmation-modal/index.test.js @@ -344,7 +344,7 @@ describe('UnavailableProductConfirmationModal', () => { test('opens confirmation modal when unavailable products are found', async () => { const mockProductIds = ['701642889899M', '701642889830M'] const mockFunc = jest.fn() - const {getByRole, user} = renderWithProviders( + const {getByText, queryByText, queryByRole, user} = renderWithProviders( { ) await waitFor(async () => { - const removeBtn = getByRole('button') - expect(removeBtn).toBeInTheDocument() - await user.click(removeBtn) - expect(mockFunc).toHaveBeenCalled() - expect(removeBtn).not.toBeInTheDocument() + expect(getByText(/Items Unavailable/i)).toBeInTheDocument() }) + const removeBtn = queryByRole('button') + + expect(removeBtn).toBeInTheDocument() + await user.click(removeBtn) + expect(mockFunc).toHaveBeenCalled() + await waitFor(async () => { + expect(queryByText(/Items Unavailable/i)).not.toBeInTheDocument() + }) + expect(removeBtn).not.toBeInTheDocument() }) })