Skip to content

Commit

Permalink
Merge branch 'plp-strikethrough-price' into vm/promo-callout
Browse files Browse the repository at this point in the history
  • Loading branch information
vmarta committed May 16, 2024
2 parents e75813f + f872153 commit cee3ed3
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 130 deletions.
Original file line number Diff line number Diff line change
@@ -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 '@salesforce/retail-react-app/app/components/display-price/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 ? (
<Text
as={as}
{...extraProps}
aria-label={intl.formatMessage(msg.ariaLabelCurrentPriceWithRange, {
currentPrice: currentPriceText
})}
>
{intl.formatMessage(msg.currentPriceWithRange, {
currentPrice: currentPriceText
})}
</Text>
) : (
<Text
as={as}
{...extraProps}
aria-label={intl.formatMessage(msg.ariaLabelCurrentPrice, {
currentPrice: currentPriceText
})}
>
{currentPriceText}
</Text>
)
}
CurrentPrice.propTypes = {
price: PropTypes.number.isRequired,
currency: PropTypes.string.isRequired,
as: PropTypes.string,
isRange: PropTypes.bool,
extraProps: PropTypes.object
}

export default CurrentPrice
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -73,101 +49,6 @@ const DisplayPrice = ({priceData, currency}) => {

return <Box>{renderPriceSet(false)}</Box>
}
/**
* 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 ? (
<Text
as={as}
{...extraProps}
aria-label={intl.formatMessage(msg.ariaLabelCurrentPriceWithRange, {
currentPrice: currentPriceText
})}
>
{intl.formatMessage(msg.currentPriceWithRange, {
currentPrice: currentPriceText
})}
</Text>
) : (
<Text
as={as}
{...extraProps}
aria-label={intl.formatMessage(msg.ariaLabelCurrentPrice, {
currentPrice: currentPriceText
})}
>
{currentPriceText}
</Text>
)
}
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 ? (
<Text
as={as}
{...extraProps}
aria-label={intl.formatMessage(msg.ariaLabelListPriceWithRange, {
listPrice: listPriceText || ''
})}
color="gray.600"
>
{listPriceText}
</Text>
) : (
<Text
as={as}
{...extraProps}
aria-label={intl.formatMessage(msg.ariaLabelListPrice, {
listPrice: listPriceText || ''
})}
color="gray.600"
>
{listPriceText}
</Text>
)
}

ListPrice.propTypes = {
price: PropTypes.number.isRequired,
currency: PropTypes.string.isRequired,
as: PropTypes.string,
isRange: PropTypes.bool,
extraProps: PropTypes.object
}

DisplayPrice.propTypes = {
priceData: PropTypes.shape({
Expand All @@ -184,4 +65,3 @@ DisplayPrice.propTypes = {
}

export default DisplayPrice
export {ListPrice, CurrentPrice, msg}
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -28,7 +30,6 @@ describe('DisplayPrice', function () {
const {container} = renderWithProviders(<DisplayPrice currency="GBP" priceData={data} />)
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)
Expand All @@ -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', () => {
renderWithProviders(<CurrentPrice price={100} currency="GBP" />)
expect(screen.getByText(/£100\.00/i)).toBeInTheDocument()
expect(screen.getByLabelText(/current price £100\.00/i)).toBeInTheDocument()
})

test('should render range price', () => {
renderWithProviders(<CurrentPrice price={100} currency="GBP" isRange={true} />)
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(<ListPrice price={100} currency="GBP" />)
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(
<ListPrice price={100} currency="GBP" isRange={true} />
)
const strikethroughPriceTag = container.querySelectorAll('s')
expect(screen.getByLabelText(/from original price £100\.00/i)).toBeInTheDocument()
expect(within(strikethroughPriceTag[0]).getByText(/£100\.00/i)).toBeDefined()
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* 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 '@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
* @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 ? (
<Text
as={as}
{...extraProps}
aria-label={intl.formatMessage(msg.ariaLabelListPriceWithRange, {
listPrice: listPriceText || ''
})}
color="gray.600"
>
{listPriceText}
</Text>
) : (
<Text
as={as}
{...extraProps}
aria-label={intl.formatMessage(msg.ariaLabelListPrice, {
listPrice: listPriceText || ''
})}
color="gray.600"
>
{listPriceText}
</Text>
)
}

ListPrice.propTypes = {
price: PropTypes.number.isRequired,
currency: PropTypes.string.isRequired,
as: PropTypes.string,
isRange: PropTypes.bool,
extraProps: PropTypes.object
}

export default ListPrice
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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({
// 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
Original file line number Diff line number Diff line change
Expand Up @@ -344,19 +344,24 @@ 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(
<UnavailableProductConfirmationModal
productIds={mockProductIds}
handleUnavailableProducts={mockFunc}
/>
)

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()
})
})

0 comments on commit cee3ed3

Please sign in to comment.