Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
65 changes: 35 additions & 30 deletions packages/template-retail-react-app/app/components/_app/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ import {DrawerMenu} from '@salesforce/retail-react-app/app/components/drawer-men
import {ListMenu, ListMenuContent} from '@salesforce/retail-react-app/app/components/list-menu'
import {HideOnDesktop, HideOnMobile} from '@salesforce/retail-react-app/app/components/responsive'
import AboveHeader from '@salesforce/retail-react-app/app/components/_app/partials/above-header'
import StoreLocatorModal from '@salesforce/retail-react-app/app/components/store-locator-modal'
import StoreLocatorModal, {
StoreLocatorProvider
} from '@salesforce/retail-react-app/app/components/store-locator-modal'

// Hooks
import {AuthModal, useAuthModal} from '@salesforce/retail-react-app/app/hooks/use-auth-modal'
import {
Expand Down Expand Up @@ -354,10 +357,6 @@ const App = (props) => {

<Box id="app" display="flex" flexDirection="column" flex={1}>
<SkipNavLink zIndex="skipLink">Skip to Content</SkipNavLink>
<StoreLocatorModal
isOpen={isOpenStoreLocator}
onClose={onCloseStoreLocator}
/>
<Box {...styles.headerWrapper}>
{!isCheckout ? (
<>
Expand Down Expand Up @@ -402,32 +401,38 @@ const App = (props) => {
</Box>
{!isOnline && <OfflineBanner />}
<AddToCartModalProvider>
<SkipNavContent
style={{
display: 'flex',
flexDirection: 'column',
flex: 1,
outline: 0
}}
>
<Box
as="main"
id="app-main"
role="main"
display="flex"
flexDirection="column"
flex="1"
<StoreLocatorProvider>
<SkipNavContent
style={{
display: 'flex',
flexDirection: 'column',
flex: 1,
outline: 0
}}
>
<OfflineBoundary isOnline={false}>
{children}
</OfflineBoundary>
</Box>
</SkipNavContent>

{!isCheckout ? <Footer /> : <CheckoutFooter />}

<AuthModal {...authModal} />
<DntNotification {...dntNotification} />
<Box
as="main"
id="app-main"
role="main"
display="flex"
flexDirection="column"
flex="1"
>
<OfflineBoundary isOnline={false}>
{children}
</OfflineBoundary>
</Box>
</SkipNavContent>

{!isCheckout ? <Footer /> : <CheckoutFooter />}

<AuthModal {...authModal} />
<StoreLocatorModal
isOpen={isOpenStoreLocator}
onClose={onCloseStoreLocator}
/>
<DntNotification {...dntNotification} />
</StoreLocatorProvider>
</AddToCartModalProvider>
</Box>
</CurrencyProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import Swatch from '@salesforce/retail-react-app/app/components/swatch-group/swa
import SwatchGroup from '@salesforce/retail-react-app/app/components/swatch-group'
import {getPriceData} from '@salesforce/retail-react-app/app/utils/product-utils'
import PromoCallout from '@salesforce/retail-react-app/app/components/product-tile/promo-callout'
import StoreAvailabilityText from '@salesforce/retail-react-app/app/components/store-availability-text'

const ProductViewHeader = ({
name,
Expand Down Expand Up @@ -118,7 +119,8 @@ const ProductView = forwardRef(
!isProductLoading && variant?.orderable && quantity > 0 && quantity <= stockLevel,
showImageGallery = true,
setSelectedBundleQuantity = () => {},
selectedBundleParentQuantity = 1
selectedBundleParentQuantity = 1,
selectedStore
},
ref
) => {
Expand Down Expand Up @@ -598,6 +600,10 @@ const ProductView = forwardRef(
/>
</VStack>
)}
<StoreAvailabilityText
selectedStore={selectedStore}
productInventories={product?.inventories}
/>
<Box ref={errorContainerRef}>
{!showLoading && showOptionsMessage && (
<Fade in={true}>
Expand Down Expand Up @@ -698,7 +704,8 @@ ProductView.propTypes = {
validateOrderability: PropTypes.func,
showImageGallery: PropTypes.bool,
setSelectedBundleQuantity: PropTypes.func,
selectedBundleParentQuantity: PropTypes.number
selectedBundleParentQuantity: PropTypes.number,
selectedStore: PropTypes.object
}

export default ProductView
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ test('ProductView Component renders properly', async () => {
expect(screen.getAllByText(/Add to cart/i)).toHaveLength(2)
expect(screen.getAllByRole('radiogroup')).toHaveLength(3)
expect(screen.getAllByText(/add to cart/i)).toHaveLength(2)
expect(screen.getByText(/In Stock at/i)).toBeInTheDocument()
expect(screen.getByText(/Select Store/i)).toBeInTheDocument()
})

test('ProductView Component renders with addToCart event handler', async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* 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 {FormattedMessage} from 'react-intl'

// Components
import {Box, Link} from '@salesforce/retail-react-app/app/components/shared/ui'

const StoreAvailabilityText = ({selectedStore, productInventories}) => {
const inStock = productInventories?.find(
(inventory) => inventory.id === selectedStore.inventoryId && inventory.orderable
)

return (
<Box gap={1} fontWeight={400} display="flex">
{!productInventories || inStock ? (
<FormattedMessage
id={'product_view.store_availability.in_stock_at'}
defaultMessage={'In Stock at'}
/>
) : (
<FormattedMessage
id={'product_view.store_availability.out_of_stock_at'}
defaultMessage={'Out of Stock at'}
/>
)}
<Link>
{selectedStore?.name ? (
selectedStore.name
) : (
<FormattedMessage
id={'product_view.link.select_store'}
defaultMessage={'Select Store'}
/>
)}
</Link>
</Box>
)
}

StoreAvailabilityText.propTypes = {
selectedStore: PropTypes.object,
productInventories: PropTypes.arrayOf(PropTypes.object)
}

export default StoreAvailabilityText
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* 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 {screen} from '@testing-library/react'

import {renderWithProviders} from '@salesforce/retail-react-app/app/utils/test-utils'
import StoreAvailabilityText from '@salesforce/retail-react-app/app/components/store-availability-text'

const selectedStore = {
name: 'Test Store',
id: 'test_store',
inventoryId: 'inventory_1'
}

describe('Store Name Rendering', () => {
test('renders "Select Store" if selectedStore does not contain a name field', () => {
renderWithProviders(<StoreAvailabilityText selectedStore={{}} />)
expect(screen.getByText(/Select Store/i)).toBeInTheDocument()
})

test('renders store name if selectedStore contains a name field', () => {
renderWithProviders(<StoreAvailabilityText selectedStore={selectedStore} />)
expect(screen.getByText(selectedStore.name)).toBeInTheDocument()
})
})

describe('Stock Availability Rendering', () => {
test('renders "In Stock" when productInventories is undefined', () => {
renderWithProviders(<StoreAvailabilityText selectedStore={selectedStore} />)
expect(screen.getByText(/In Stock at/i)).toBeInTheDocument()
})

test('renders "In Stock" if orderable is true', () => {
renderWithProviders(
<StoreAvailabilityText
selectedStore={selectedStore}
productInventories={[{id: 'inventory_1', orderable: true}]}
/>
)
expect(screen.getByText(/In Stock at/i)).toBeInTheDocument()
})

test('renders "Out of Stock" if orderable is false', () => {
renderWithProviders(
<StoreAvailabilityText
selectedStore={selectedStore}
productInventories={[{id: 'inventory_1', orderable: false}]}
/>
)
expect(screen.getByText(/Out of Stock at/i)).toBeInTheDocument()
})

test('renders "Out of Stock" if selectedStore inventoryId is not in productInventories', () => {
renderWithProviders(
<StoreAvailabilityText
selectedStore={selectedStore}
productInventories={[{id: 'inventory_99', orderable: true}]}
/>
)
expect(screen.getByText(/Out of Stock at/i)).toBeInTheDocument()
})
})
Loading
Loading