Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -68,7 +68,7 @@ import CVVSymbol from '@salesforce/retail-react-app/app/assets/svg/cc-cvv.svg'
import DiscoverSymbol from '@salesforce/retail-react-app/app/assets/svg/cc-discover.svg'
import LocationSymbol from '@salesforce/retail-react-app/app/assets/svg/location.svg'
import MastercardSymbol from '@salesforce/retail-react-app/app/assets/svg/cc-mastercard.svg'
import PaypalSymbol from '@salesforce/retail-react-app/app/assets/svg/paypal.svg'
import PaypalSymbol from '@salesforce/retail-react-app/app/assets/svg/paypal-icon.svg'
import SocialPinterestSymbol from '@salesforce/retail-react-app/app/assets/svg/social-pinterest.svg'
import VisaSymbol from '@salesforce/retail-react-app/app/assets/svg/cc-visa.svg'

Expand Down Expand Up @@ -186,7 +186,7 @@ export const LockIcon = icon(
}
)
export const LocationIcon = icon('location')
export const PaypalIcon = icon('paypal', {viewBox: PaypalSymbol.viewBox})
export const PaypalIcon = icon('paypal-icon', {viewBox: PaypalSymbol.viewBox})
export const PlugIcon = icon('plug')
export const PlusIcon = icon('plus')
export const MastercardIcon = icon('cc-mastercard', {viewBox: MastercardSymbol.viewBox})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import {useAddToCartModalContext} from '@salesforce/retail-react-app/app/hooks/u
import {STORE_LOCATOR_IS_ENABLED} from '@salesforce/retail-react-app/app/constants'
import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config'
import {useShopperBasketsMutationHelper} from '@salesforce/commerce-sdk-react'
import {useSalesforcePayments} from '@salesforce/retail-react-app/app/hooks/use-salesforce-payments'
import {useShopperConfiguration} from '@salesforce/retail-react-app/app/hooks/use-shopper-configuration'

// project components
import ImageGallery from '@salesforce/retail-react-app/app/components/image-gallery'
Expand Down Expand Up @@ -199,7 +199,7 @@ const ProductView = forwardRef(
const [pickupEnabled, setPickupEnabled] = useState(false)
const storeName = selectedStore?.name
const inventoryId = selectedStore?.inventoryId
const sfPaymentsEnabled = useSalesforcePayments()
const sfPaymentsEnabled = useShopperConfiguration('SalesforcePaymentsAllowed') === true
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we put this useShopperConfiguration('SalesforcePaymentsAllowed') === true check in a place where it doesn't have to be duplicated? For example if a thing is already importing useSalesforcePayments could that hook result include an enabled property?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

took a note to address this in my next iteration


const {disableButton, customInventoryMessage} = useMemo(() => {
let shouldDisableButton = showInventoryMessage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import {EINSTEIN_RECOMMENDERS} from '@salesforce/retail-react-app/app/constants'
import DisplayPrice from '@salesforce/retail-react-app/app/components/display-price'
import SFPaymentsExpress from '@salesforce/retail-react-app/app/components/sf-payments-express'
import SelectBonusProductsCard from '@salesforce/retail-react-app/app/pages/cart/partials/select-bonus-products-card'
import {useSalesforcePayments} from '@salesforce/retail-react-app/app/hooks/use-salesforce-payments'
import {useShopperConfiguration} from '@salesforce/retail-react-app/app/hooks/use-shopper-configuration'

import {
getRemainingAvailableBonusProductsForProduct,
Expand Down Expand Up @@ -85,7 +85,7 @@ export const AddToCartModal = () => {
: Array.isArray(itemsAdded)
? itemsAdded.reduce((acc, {quantity}) => acc + quantity, 0)
: 0
const sfPaymentsEnabled = useSalesforcePayments()
const sfPaymentsEnabled = useShopperConfiguration('SalesforcePaymentsAllowed') === true

// Bonus product logic
const {data: productsWithPromotions} = useBasketProductsWithPromotions(basket)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,14 @@ jest.mock('@salesforce/retail-react-app/app/hooks/use-navigation', () => ({
// Mock getConfig for SF Payments configuration
const mockGetConfig = jest.fn()

// Mock useSalesforcePayments to respond to config
jest.mock('@salesforce/retail-react-app/app/hooks/use-salesforce-payments', () => ({
useSalesforcePayments: jest.fn(() => {
// Mock useShopperConfiguration to respond to config
jest.mock('@salesforce/retail-react-app/app/hooks/use-shopper-configuration', () => ({
useShopperConfiguration: jest.fn((configId) => {
const config = mockGetConfig()
return config?.app?.sfPayments?.enabled === true
if (configId === 'SalesforcePaymentsAllowed') {
return config?.app?.sfPayments?.enabled
}
return undefined
})
}))
jest.mock('@salesforce/pwa-kit-runtime/utils/ssr-config', () => {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* 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 {useConfigurations} from '@salesforce/commerce-sdk-react'

/**
* Hook to get a shopper configuration value.
* @param {string} configurationId - The ID of the configuration to retrieve
* @returns {*} The configuration value, or undefined if not found
*/
export const useShopperConfiguration = (configurationId) => {
const {data: configurations} = useConfigurations()
const config = configurations?.configurations?.find(
(configuration) => configuration.id === configurationId
)
return config?.value
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
/*
* 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 {renderHook} from '@testing-library/react'
import {useShopperConfiguration} from '@salesforce/retail-react-app/app/hooks/use-shopper-configuration'
import {useConfigurations} from '@salesforce/commerce-sdk-react'

// Mock the commerce-sdk-react hook
jest.mock('@salesforce/commerce-sdk-react', () => ({
useConfigurations: jest.fn()
}))

describe('useShopperConfiguration', () => {
afterEach(() => {
jest.clearAllMocks()
})

test('returns the configuration value when configuration exists with boolean true', () => {
useConfigurations.mockReturnValue({
data: {
configurations: [
{id: 'SalesforcePaymentsAllowed', value: true},
{id: 'AnotherConfig', value: false}
]
}
})

const {result} = renderHook(() => useShopperConfiguration('SalesforcePaymentsAllowed'))

expect(result.current).toBe(true)
})

test('returns the configuration value when configuration exists with boolean false', () => {
useConfigurations.mockReturnValue({
data: {
configurations: [{id: 'SomeConfig', value: false}]
}
})

const {result} = renderHook(() => useShopperConfiguration('SomeConfig'))

expect(result.current).toBe(false)
})

test('returns the configuration value when configuration exists with string value', () => {
useConfigurations.mockReturnValue({
data: {
configurations: [{id: 'StringConfig', value: 'some-string-value'}]
}
})

const {result} = renderHook(() => useShopperConfiguration('StringConfig'))

expect(result.current).toBe('some-string-value')
})

test('returns the configuration value when configuration exists with numeric value', () => {
useConfigurations.mockReturnValue({
data: {
configurations: [{id: 'NumericConfig', value: 42}]
}
})

const {result} = renderHook(() => useShopperConfiguration('NumericConfig'))

expect(result.current).toBe(42)
})

test('returns the configuration value when configuration exists with object value', () => {
const objectValue = {key: 'value', nested: {prop: 'test'}}
useConfigurations.mockReturnValue({
data: {
configurations: [{id: 'ObjectConfig', value: objectValue}]
}
})

const {result} = renderHook(() => useShopperConfiguration('ObjectConfig'))

expect(result.current).toEqual(objectValue)
})

test('returns undefined when configuration does not exist', () => {
useConfigurations.mockReturnValue({
data: {
configurations: [
{id: 'Config1', value: true},
{id: 'Config2', value: false}
]
}
})

const {result} = renderHook(() => useShopperConfiguration('NonExistentConfig'))

expect(result.current).toBeUndefined()
})

test('returns undefined when configurations data is undefined', () => {
useConfigurations.mockReturnValue({
data: undefined
})

const {result} = renderHook(() => useShopperConfiguration('SomeConfig'))

expect(result.current).toBeUndefined()
})

test('returns undefined when configurations data is null', () => {
useConfigurations.mockReturnValue({
data: null
})

const {result} = renderHook(() => useShopperConfiguration('SomeConfig'))

expect(result.current).toBeUndefined()
})

test('returns undefined when configurations array is undefined', () => {
useConfigurations.mockReturnValue({
data: {
configurations: undefined
}
})

const {result} = renderHook(() => useShopperConfiguration('SomeConfig'))

expect(result.current).toBeUndefined()
})

test('returns undefined when configurations array is empty', () => {
useConfigurations.mockReturnValue({
data: {
configurations: []
}
})

const {result} = renderHook(() => useShopperConfiguration('SomeConfig'))

expect(result.current).toBeUndefined()
})

test('returns the correct configuration when multiple configurations exist', () => {
useConfigurations.mockReturnValue({
data: {
configurations: [
{id: 'Config1', value: 'value1'},
{id: 'Config2', value: 'value2'},
{id: 'Config3', value: 'value3'}
]
}
})

const {result} = renderHook(() => useShopperConfiguration('Config2'))

expect(result.current).toBe('value2')
})

test('returns undefined when configuration exists but has no value property', () => {
useConfigurations.mockReturnValue({
data: {
configurations: [{id: 'ConfigWithoutValue'}]
}
})

const {result} = renderHook(() => useShopperConfiguration('ConfigWithoutValue'))

expect(result.current).toBeUndefined()
})

test('returns null when configuration value is explicitly null', () => {
useConfigurations.mockReturnValue({
data: {
configurations: [{id: 'NullConfig', value: null}]
}
})

const {result} = renderHook(() => useShopperConfiguration('NullConfig'))

expect(result.current).toBeNull()
})

test('returns 0 when configuration value is 0', () => {
useConfigurations.mockReturnValue({
data: {
configurations: [{id: 'ZeroConfig', value: 0}]
}
})

const {result} = renderHook(() => useShopperConfiguration('ZeroConfig'))

expect(result.current).toBe(0)
})

test('returns empty string when configuration value is empty string', () => {
useConfigurations.mockReturnValue({
data: {
configurations: [{id: 'EmptyStringConfig', value: ''}]
}
})

const {result} = renderHook(() => useShopperConfiguration('EmptyStringConfig'))

expect(result.current).toBe('')
})

test('is case-sensitive when matching configuration IDs', () => {
useConfigurations.mockReturnValue({
data: {
configurations: [
{id: 'SalesforcePaymentsAllowed', value: true},
{id: 'salesforcepaymentsallowed', value: false}
]
}
})

const {result: result1} = renderHook(() =>
useShopperConfiguration('SalesforcePaymentsAllowed')
)
const {result: result2} = renderHook(() =>
useShopperConfiguration('salesforcepaymentsallowed')
)

expect(result1.current).toBe(true)
expect(result2.current).toBe(false)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ import {
} from '@salesforce/retail-react-app/app/components/icons'
import Link from '@salesforce/retail-react-app/app/components/link'
import SFPaymentsExpress from '@salesforce/retail-react-app/app/components/sf-payments-express'
import {useSalesforcePayments} from '@salesforce/retail-react-app/app/hooks/use-salesforce-payments'
import {useShopperConfiguration} from '@salesforce/retail-react-app/app/hooks/use-shopper-configuration'

const CartCta = () => {
const sfPaymentsEnabled = useSalesforcePayments()
const sfPaymentsEnabled = useShopperConfiguration('SalesforcePaymentsAllowed') === true

return (
<Fragment>
Expand Down
Loading
Loading