Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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, {useState} from 'react'
import {FormattedMessage, FormattedNumber, useIntl} from 'react-intl'
import {FormattedMessage, useIntl} from 'react-intl'
import PropTypes from 'prop-types'
import {
Box,
Expand All @@ -17,13 +17,11 @@ import {
Text,
Tooltip
} from '@salesforce/retail-react-app/app/components/shared/ui'
import {useCurrentBasket} from '@salesforce/retail-react-app/app/hooks/use-current-basket'
import {LockIcon, PaypalIcon} from '@salesforce/retail-react-app/app/components/icons'
import CreditCardFields from '@salesforce/retail-react-app/app/components/forms/credit-card-fields'
import {useCurrency} from '@salesforce/retail-react-app/app/hooks'
import {getCreditCardIcon} from '@salesforce/retail-react-app/app/utils/cc-utils'

const INITIAL_DISPLAYED_SAVED_PAYMENT_INSTRUMENTS = 1
const INITIAL_DISPLAYED_SAVED_PAYMENT_INSTRUMENTS = 3

const PaymentCardSummary = ({payment}) => {
const CardIcon = getCreditCardIcon(payment?.paymentCard?.cardType)
Expand Down Expand Up @@ -54,20 +52,22 @@ const PaymentForm = ({
selectedPaymentMethod
}) => {
const {formatMessage} = useIntl()
const {data: basket} = useCurrentBasket()
const {currency} = useCurrency()
const [showAllPaymentInstruments, setShowAllPaymentInstruments] = useState(false)

const hasSavedPaymentInstruments = savedPaymentInstruments?.length > 0
const savedCount = savedPaymentInstruments?.length || 0
const totalItems = savedCount + 2 // saved + credit card + paypal
const n = showAllPaymentInstruments ? totalItems : INITIAL_DISPLAYED_SAVED_PAYMENT_INSTRUMENTS

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: a more readable/meaningful variable name


const displayedSavedCount = Math.min(savedCount, n)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't the savedCount is it? n could be total items which is saved + hardcoded methods paypall and new CC

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the count of SPMs shown in the collapsed view. Min of SPMs and n which is either 3 when collapsed or total(SPMs + CC + PayPal) when expanded.

const displayedSavedPaymentInstruments =
savedPaymentInstruments?.slice(
0,
showAllPaymentInstruments
? savedPaymentInstruments.length
: INITIAL_DISPLAYED_SAVED_PAYMENT_INSTRUMENTS
) || []
const isDisplayingAllPaymentInstruments =
displayedSavedPaymentInstruments?.length === (savedPaymentInstruments?.length || 0)
savedPaymentInstruments?.slice(0, displayedSavedCount) || []

const showCreditCard = n > displayedSavedCount
const displayedAfterCC = displayedSavedCount + (showCreditCard ? 1 : 0)
const showPaypal = n > displayedAfterCC

const showViewAllButton =
totalItems > INITIAL_DISPLAYED_SAVED_PAYMENT_INSTRUMENTS && !showAllPaymentInstruments

return (
<form onSubmit={form.handleSubmit(onSubmit)}>
Expand Down Expand Up @@ -101,78 +101,79 @@ const PaymentForm = ({
</Box>
))}

<Box
py={3}
px={[4, 4, 6]}
bg="gray.50"
borderBottom="1px solid"
borderColor="gray.100"
>
<Radio value="cc">
<Flex justify="space-between">
<Stack direction="row" align="center">
<Text fontWeight="bold">
<FormattedMessage
defaultMessage="Credit Card"
id="payment_selection.heading.credit_card"
/>
</Text>
<Tooltip
hasArrow
placement="top"
label={formatMessage({
defaultMessage:
'This is a secure SSL encrypted payment.',
id: 'payment_selection.tooltip.secure_payment'
})}
>
<LockIcon color="gray.700" boxSize={5} />
</Tooltip>
</Stack>
<Text fontWeight="bold">
<FormattedNumber
value={basket?.orderTotal}
style="currency"
currency={currency}
/>
</Text>
</Flex>
</Radio>
</Box>
<Collapse in={selectedPaymentMethod === 'cc'} animateOpacity>
<Box p={[4, 4, 6]} borderBottom="1px solid" borderColor="gray.100">
<Stack spacing={6}>
<Stack spacing={6}>
<CreditCardFields form={form} />
</Stack>
{children && <Box pt={2}>{children}</Box>}
</Stack>
</Box>
</Collapse>

<Box py={3} px={[4, 4, 6]} bg="gray.50" borderColor="gray.100">
<Radio value="paypal">
<Box py="2px">
<PaypalIcon width="auto" height="20px" />
{showCreditCard && (
<>
<Box
py={3}
px={[4, 4, 6]}
bg="gray.50"
borderBottom="1px solid"
borderColor="gray.100"
>
<Radio value="cc">
<Flex justify="space-between">
<Stack direction="row" align="center">
<Text fontWeight="bold">
<FormattedMessage
defaultMessage="Credit Card"
id="payment_selection.heading.credit_card"
/>
</Text>
<Tooltip
hasArrow
placement="top"
label={formatMessage({
defaultMessage:
'This is a secure SSL encrypted payment.',
id: 'payment_selection.tooltip.secure_payment'
})}
>
<LockIcon color="gray.700" boxSize={5} />
</Tooltip>
</Stack>
</Flex>
</Radio>
</Box>
</Radio>
</Box>
<Collapse in={selectedPaymentMethod === 'cc'} animateOpacity>
<Box
p={[4, 4, 6]}
borderBottom="1px solid"
borderColor="gray.100"
>
<Stack spacing={6}>
<Stack spacing={6}>
<CreditCardFields form={form} />
</Stack>
{children && <Box pt={2}>{children}</Box>}
</Stack>
</Box>
</Collapse>
</>
)}

{showPaypal && (
<Box py={3} px={[4, 4, 6]} bg="gray.50" borderColor="gray.100">
<Radio value="paypal">
<Box py="2px">
<PaypalIcon width="auto" height="20px" />
</Box>
</Radio>
</Box>
)}
</RadioGroup>
</Box>
{!isDisplayingAllPaymentInstruments && hasSavedPaymentInstruments && (
{showViewAllButton && savedCount > 0 && (
<Box py={3} px={[4, 4, 6]}>
<button
onClick={() =>
setShowAllPaymentInstruments(!showAllPaymentInstruments)
}
type="button"
data-testid="view-all-saved-payments"
onClick={() => setShowAllPaymentInstruments(true)}
>
<FormattedMessage
defaultMessage="View All ({count} more)"
id="payment_selection.button.view_all"
values={{
count:
savedPaymentInstruments?.length -
INITIAL_DISPLAYED_SAVED_PAYMENT_INSTRUMENTS
count: Math.max(totalItems - n, 0)
}}
/>
</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import React from 'react'
import {render, screen} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import {useCurrentBasket} from '@salesforce/retail-react-app/app/hooks/use-current-basket'
import {useCurrency} from '@salesforce/retail-react-app/app/hooks'
import PaymentForm from '@salesforce/retail-react-app/app/pages/checkout-one-click/partials/one-click-payment-form'
Expand Down Expand Up @@ -111,12 +112,6 @@ describe('PaymentForm Component', () => {
expect(screen.getByTestId('paypal-icon')).toBeInTheDocument()
})

test('displays order total with currency formatting', () => {
render(<PaymentForm form={mockForm} onSubmit={jest.fn()} />)

expect(screen.getByText('USD99.99')).toBeInTheDocument()
})

test('shows security lock icon with tooltip', () => {
render(<PaymentForm form={mockForm} onSubmit={jest.fn()} />)

Expand Down Expand Up @@ -183,8 +178,8 @@ describe('PaymentForm Component', () => {

// Check that saved payment methods are rendered
expect(screen.getByDisplayValue('saved-payment-1')).toBeInTheDocument()
// we only show 1 saved payment method up front. User has to click Show All to see the second one
expect(screen.queryByDisplayValue('saved-payment-2')).not.toBeInTheDocument()
// With unified collapsed view (n=3), both saved methods are initially visible
expect(screen.getByDisplayValue('saved-payment-2')).toBeInTheDocument()
})

test('displays saved payment method details correctly', () => {
Expand Down Expand Up @@ -285,7 +280,7 @@ describe('PaymentForm Component', () => {
}).not.toThrow()
})

test('renders saved payment methods between credit card and PayPal options', () => {
test('renders saved payment methods between credit card and PayPal options', async () => {
render(
<PaymentForm
form={mockForm}
Expand All @@ -294,10 +289,14 @@ describe('PaymentForm Component', () => {
/>
)

// Expand to ensure PayPal is visible in the list
const showAllButton = screen.getByTestId('view-all-saved-payments')
await userEvent.click(showAllButton)

const radioButtons = screen.getAllByRole('radio')
const values = radioButtons.map((radio) => radio.value)

// Should have credit card, saved payments, and PayPal in order
// Should include credit card, saved payments, and PayPal
expect(values).toContain('cc')
expect(values).toContain('saved-payment-1')
expect(values).toContain('paypal')
Expand All @@ -312,9 +311,15 @@ describe('PaymentForm Component', () => {
/>
)

// Should render card icons for each saved payment method
const cardIcons = screen.getAllByTestId('card-icon')
expect(cardIcons).toHaveLength(1)
// Should render card icons for each initially visible saved payment method (max 3)
let cardIcons = screen.getAllByTestId('card-icon')
expect(cardIcons).toHaveLength(2)

// Expand and assert all saved payment icons render
const showAllButton = screen.getByText('payment_selection.button.view_all')
showAllButton.click()
cardIcons = screen.getAllByTestId('card-icon')
expect(cardIcons).toHaveLength(mockSavedPaymentInstruments.length)
})

describe('Show All Payment Instruments', () => {
Expand Down Expand Up @@ -392,9 +397,36 @@ describe('PaymentForm Component', () => {
/>
)

// Should render card icons for each saved payment method
// Should render card icons for each initially visible saved payment method (max 3)
const cardIcons = screen.getAllByTestId('card-icon')
expect(cardIcons).toHaveLength(1)
expect(cardIcons).toHaveLength(2)
})

test('hides CC/PayPal when there are 3 or more saved methods (collapsed)', () => {
const threeSaved = [
...mockSavedPaymentInstruments,
{
paymentInstrumentId: 'saved-payment-3',
paymentCard: {
cardType: 'Visa',
numberLastDigits: '9012',
expirationMonth: '03',
expirationYear: '30'
}
}
]

render(
<PaymentForm
form={mockForm}
onSubmit={jest.fn()}
savedPaymentInstruments={threeSaved}
/>
)

// Collapsed should show first 3 saved only, not CC/PayPal
expect(screen.queryByDisplayValue('cc')).not.toBeInTheDocument()
expect(screen.queryByDisplayValue('paypal')).not.toBeInTheDocument()
})
})
})
Expand All @@ -406,8 +438,9 @@ describe('PaymentForm Component', () => {
})

render(<PaymentForm form={mockForm} onSubmit={jest.fn()} />)

expect(screen.getByText('USD0.00')).toBeInTheDocument()
expect(
screen.getByLabelText('payment_selection.radio_group.assistive_msg')
).toBeInTheDocument()
})

test('handles basket with null total', () => {
Expand All @@ -416,32 +449,36 @@ describe('PaymentForm Component', () => {
})

render(<PaymentForm form={mockForm} onSubmit={jest.fn()} />)

expect(screen.getByText('USD0.00')).toBeInTheDocument()
expect(
screen.getByLabelText('payment_selection.radio_group.assistive_msg')
).toBeInTheDocument()
})

test('handles different currency', () => {
useCurrency.mockReturnValue({currency: 'EUR'})

render(<PaymentForm form={mockForm} onSubmit={jest.fn()} />)

expect(screen.getByText('EUR99.99')).toBeInTheDocument()
expect(
screen.getByLabelText('payment_selection.radio_group.assistive_msg')
).toBeInTheDocument()
})

test('handles missing basket data', () => {
useCurrentBasket.mockReturnValue({data: null})

render(<PaymentForm form={mockForm} onSubmit={jest.fn()} />)

expect(screen.getByText('USD0.00')).toBeInTheDocument()
expect(
screen.getByLabelText('payment_selection.radio_group.assistive_msg')
).toBeInTheDocument()
})

test('handles undefined basket', () => {
useCurrentBasket.mockReturnValue({data: undefined})

render(<PaymentForm form={mockForm} onSubmit={jest.fn()} />)

expect(screen.getByText('USD0.00')).toBeInTheDocument()
expect(
screen.getByLabelText('payment_selection.radio_group.assistive_msg')
).toBeInTheDocument()
})
})

Expand Down
Loading
Loading