-
Notifications
You must be signed in to change notification settings - Fork 212
@ W-18953852: Merge Feature/auto bonus product in to Develop #2704
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 48 commits
802b9d3
790a72b
c2021fa
0aadd8b
69d49b2
bfb54b2
a47d9b0
c693bea
92ec48e
84775e5
7830475
83b1dbb
0a697ef
4a0950c
5c7b0bb
afb7dc9
2600c99
bd8f645
392c52f
90226ae
9414838
f8fd65e
e441ee9
4848f14
3e7d1f9
9f6cfe5
06f0de5
4f8a0f5
fb4c351
bc28fc3
be2eb2b
8e0a562
21771bd
5db3f9c
3826c2d
e29bcd2
a9d065d
48e883c
8d640d0
f3d883c
d1f7b59
a9e65e2
c6536e2
90be03e
2a05b9f
09dac15
a4f9d6f
71f3090
09b5ccf
755f63f
adb5603
c6e15a9
facc6d0
5f37a75
3c2b693
99e8c91
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -73,3 +73,80 @@ test('component renders product bundles without variant data', async () => { | |
| }) | ||
| }) | ||
| }) | ||
|
|
||
| // Helper function to render the component with a given variant and props | ||
| const renderComponent = (variant, props = {}) => { | ||
| renderWithProviders( | ||
| <ItemVariantProvider variant={variant}> | ||
| <ItemAttributes {...props} /> | ||
| </ItemVariantProvider> | ||
| ) | ||
| } | ||
|
|
||
| test('renders Bonus Product when bonusProductLineItem is true', async () => { | ||
|
||
| const mockVariantWithBonusProduct = { | ||
| ...mockBundledProductItemsVariant, | ||
| bonusProductLineItem: true // Simulate the bonus product flag | ||
| } | ||
|
|
||
| // Case 1: excludeBonusLabel is true | ||
| renderComponent(mockVariantWithBonusProduct, {excludeBonusLabel: true}) | ||
|
|
||
| await waitFor(() => { | ||
| expect(screen.queryByText(/Bonus Product/i)).not.toBeInTheDocument() // Fixed assertion | ||
| }) | ||
|
|
||
| // Case 3: excludeBonusLabel is false | ||
| renderComponent(mockVariantWithBonusProduct, {excludeBonusLabel: false}) | ||
|
|
||
| await waitFor(() => { | ||
| expect(screen.getByText(/Bonus Product/i)).toBeInTheDocument() | ||
| }) | ||
| }) | ||
|
|
||
| test('renders Bonus Product when excludeBonusLabel is not set', async () => { | ||
| // Case 1: Variant has no bonus product | ||
| const mockVariantWithOutBonusProduct = { | ||
| ...mockBundledProductItemsVariant, | ||
| bonusProductLineItem: false // Simulate the bonus product flag | ||
| } | ||
|
|
||
| renderComponent(mockVariantWithOutBonusProduct) | ||
|
|
||
| await waitFor(() => { | ||
| expect(screen.queryByText(/Bonus Product/i)).not.toBeInTheDocument() // Fixed assertion | ||
| }) | ||
|
|
||
| // Case 2: Variant has bonus product | ||
| const mockVariantWithBonusProduct = { | ||
| ...mockBundledProductItemsVariant, | ||
| bonusProductLineItem: true // Simulate the bonus product flag | ||
| } | ||
|
|
||
| renderComponent(mockVariantWithBonusProduct) | ||
|
|
||
| await waitFor(() => { | ||
| expect(screen.queryByText(/Bonus Product/i)).toBeInTheDocument() // Fixed assertion | ||
|
||
| }) | ||
| }) | ||
|
|
||
| test('does not render Bonus Product when bonusProductLineItem is false', async () => { | ||
| const mockVariantWithoutBonusProduct = { | ||
| ...mockBundledProductItemsVariant, | ||
| bonusProductLineItem: false // Simulate the absence of the bonus product flag | ||
| } | ||
|
|
||
| // Case 1: excludeBonusLabel is true | ||
| renderComponent(mockVariantWithoutBonusProduct, {excludeBonusLabel: true}) | ||
|
|
||
| await waitFor(() => { | ||
| expect(screen.queryByText(/Bonus Product/i)).not.toBeInTheDocument() | ||
| }) | ||
|
|
||
| // Case 2: excludeBonusLabel is false | ||
| renderComponent(mockVariantWithoutBonusProduct, {excludeBonusLabel: false}) | ||
|
|
||
| await waitFor(() => { | ||
| expect(screen.queryByText(/Bonus Product/i)).not.toBeInTheDocument() | ||
| }) | ||
| }) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| /* | ||
| * Copyright (c) 2025, 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 | ||
| */ | ||
| import React from 'react' | ||
| import PropTypes from 'prop-types' | ||
| import {FormattedMessage, useIntl} from 'react-intl' | ||
| import {Text} from '@salesforce/retail-react-app/app/components/shared/ui' | ||
|
|
||
| const BonusProductQuantity = ({product}) => { | ||
| const intl = useIntl() | ||
| return ( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What happens if product is
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @alexvuong done |
||
| <Text | ||
| fontSize="sm" | ||
| color="gray.700" | ||
| aria-label={intl.formatMessage( | ||
| { | ||
| id: 'item_variant.quantity.label', | ||
| defaultMessage: | ||
| 'Quantity selector for {productName}. Selected quantity is {quantity}' | ||
| }, | ||
| { | ||
| quantity: product?.quantity, | ||
| productName: product?.name | ||
| } | ||
| )} | ||
| > | ||
| <FormattedMessage | ||
| defaultMessage="Quantity: {quantity}" | ||
| id="bonus_product_item.label.quantity" | ||
| values={{quantity: product?.quantity}} | ||
| /> | ||
| </Text> | ||
| ) | ||
| } | ||
|
|
||
| BonusProductQuantity.propTypes = { | ||
| product: PropTypes.object.isRequired | ||
| } | ||
|
|
||
| export default BonusProductQuantity | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is great, I love small components with a very specific purpose. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| /* | ||
| * Copyright (c) 2025, 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 | ||
| */ | ||
| import React from 'react' | ||
| import {render, screen} from '@testing-library/react' | ||
| import {IntlProvider} from 'react-intl' | ||
| import BonusProductQuantity from '@salesforce/retail-react-app/app/components/product-item/bonus-product-quantity' | ||
|
|
||
| const mockProduct = {quantity: 1} | ||
|
|
||
| const renderWithIntl = (component) => | ||
| render( | ||
| <IntlProvider locale="en" defaultLocale="en"> | ||
| {component} | ||
| </IntlProvider> | ||
| ) | ||
|
|
||
| describe('BonusProductQuantity', () => { | ||
| test('renders the quantity text', () => { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a test when skeleton is rendering
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. skeleton test added |
||
| renderWithIntl(<BonusProductQuantity product={mockProduct} />) | ||
| expect(screen.getByText(/Quantity: 1/)).toBeInTheDocument() | ||
| }) | ||
|
|
||
| test('applies correct aria-label', () => { | ||
| renderWithIntl(<BonusProductQuantity product={mockProduct} />) | ||
| const quantityElement = screen.getByText(/Quantity: 1/) | ||
| expect(quantityElement).toHaveAttribute('aria-label') | ||
| }) | ||
| }) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,17 +6,9 @@ | |
| */ | ||
| import React from 'react' | ||
| import PropTypes from 'prop-types' | ||
| import {FormattedMessage, useIntl} from 'react-intl' | ||
|
|
||
| // Chakra Components | ||
| import { | ||
| Box, | ||
| Fade, | ||
| Flex, | ||
| Stack, | ||
| Text, | ||
| VisuallyHidden | ||
| } from '@salesforce/retail-react-app/app/components/shared/ui' | ||
| import {Box, Fade, Flex, Stack, Text} from '@salesforce/retail-react-app/app/components/shared/ui' | ||
|
|
||
| // Project Components | ||
| import {HideOnDesktop, HideOnMobile} from '@salesforce/retail-react-app/app/components/responsive' | ||
|
|
@@ -26,7 +18,8 @@ import CartItemVariantName from '@salesforce/retail-react-app/app/components/ite | |
| import CartItemVariantAttributes from '@salesforce/retail-react-app/app/components/item-variant/item-attributes' | ||
| import CartItemVariantPrice from '@salesforce/retail-react-app/app/components/item-variant/item-price' | ||
| import LoadingSpinner from '@salesforce/retail-react-app/app/components/loading-spinner' | ||
| import QuantityPicker from '@salesforce/retail-react-app/app/components/quantity-picker' | ||
| import BonusProductQuantity from '@salesforce/retail-react-app/app/components/product-item/bonus-product-quantity' | ||
| import ProductQuantityPicker from '@salesforce/retail-react-app/app/components/product-item/product-quantity-picker' | ||
|
|
||
| // Utilities | ||
| import {noop} from '@salesforce/retail-react-app/app/utils/utils' | ||
|
|
@@ -53,7 +46,6 @@ const ProductItem = ({ | |
| const {stepQuantity, showInventoryMessage, inventoryMessage, quantity, setQuantity} = | ||
| useDerivedProduct(product) | ||
| const {currency: activeCurrency} = useCurrency() | ||
| const intl = useIntl() | ||
| return ( | ||
| <Box | ||
| position="relative" | ||
|
|
@@ -67,7 +59,7 @@ const ProductItem = ({ | |
| <Stack spacing={3} flex={1}> | ||
| <Stack spacing={1}> | ||
| <CartItemVariantName /> | ||
| <CartItemVariantAttributes /> | ||
| <CartItemVariantAttributes excludeBonusLabel /> | ||
kzheng-sfdc marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| <HideOnDesktop> | ||
| <Box marginTop={2}> | ||
| <CartItemVariantPrice | ||
|
|
@@ -80,67 +72,17 @@ const ProductItem = ({ | |
|
|
||
| <Flex align="flex-end" justify="space-between"> | ||
| <Stack spacing={1}> | ||
| <Text | ||
| fontSize="sm" | ||
| color="gray.700" | ||
| aria-label={intl.formatMessage( | ||
| { | ||
| id: 'item_variant.quantity.label', | ||
| defaultMessage: | ||
| 'Quantity selector for {productName}. Selected quantity is {quantity}' | ||
| }, | ||
| { | ||
| quantity: product?.quantity, | ||
| productName: product?.name | ||
| } | ||
| )} | ||
| > | ||
| <FormattedMessage | ||
| defaultMessage="Quantity:" | ||
| id="product_item.label.quantity" | ||
| {product.bonusProductLineItem ? ( | ||
| <BonusProductQuantity product={product} /> | ||
| ) : ( | ||
| <ProductQuantityPicker | ||
| product={product} | ||
| onItemQuantityChange={onItemQuantityChange} | ||
| stepQuantity={stepQuantity} | ||
| quantity={quantity} | ||
| setQuantity={setQuantity} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great job on separating these logic into different components. 👍 |
||
| /> | ||
| </Text> | ||
| <QuantityPicker | ||
| step={stepQuantity} | ||
| value={quantity} | ||
| min={0} | ||
| clampValueOnBlur={false} | ||
| onBlur={(e) => { | ||
| // Default to last known quantity if a user leaves the box with an invalid value | ||
| const {value} = e.target | ||
|
|
||
| if (!value) { | ||
| setQuantity(product.quantity) | ||
| } | ||
| }} | ||
| onChange={(stringValue, numberValue) => { | ||
| // Set the Quantity of product to value of input if value number | ||
| if (numberValue >= 0) { | ||
| // Call handler | ||
| onItemQuantityChange(numberValue).then( | ||
| (isValidChange) => | ||
| isValidChange && setQuantity(numberValue) | ||
| ) | ||
| } else if (stringValue === '') { | ||
| // We want to allow the use to clear the input to start a new input so here we set the quantity to '' so NAN is not displayed | ||
| // User will not be able to add '' quantity to the cart due to the add to cart button enablement rules | ||
| setQuantity(stringValue) | ||
| } | ||
| }} | ||
| productName={product?.name} | ||
| /> | ||
| <VisuallyHidden role="status"> | ||
| {product?.name} | ||
| {intl.formatMessage( | ||
| { | ||
| id: 'item_variant.assistive_msg.quantity', | ||
| defaultMessage: 'Quantity {quantity}' | ||
| }, | ||
| { | ||
| quantity: product?.quantity | ||
| } | ||
| )} | ||
| </VisuallyHidden> | ||
| )} | ||
| </Stack> | ||
| <Stack> | ||
| <HideOnMobile> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we have different kind of bonus ( I think there will be more type of bonus coming in?), how about we make clearer?.
Show auto bonus....