Skip to content

Commit

Permalink
Merge pull request #2225 from SalesforceCommerceCloud/W-17273365-stor…
Browse files Browse the repository at this point in the history
…e-locator-extension-integration

Store locator extension integration @@W-17273365@@
  • Loading branch information
kevinxh authored Jan 30, 2025
2 parents 97e901f + 71aa681 commit 3289701
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 18 deletions.
6 changes: 5 additions & 1 deletion packages/extension-chakra-store-locator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
"prop-types": "^15",
"react": "^18",
"react-dom": "^18",
"react-router-dom": "^5.3.4"
"react-router-dom": "^5.3.4",
"react-hook-form": "^7"
},
"peerDependenciesMeta": {
"@chakra-ui/react": {
Expand All @@ -53,6 +54,9 @@
},
"react-dom": {
"optional": true
},
"react-hook-form": {
"optional": true
}
},
"devDependencies": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,29 @@ import React from 'react'
import {render, screen} from '@testing-library/react'
import {withStoreLocator} from './with-store-locator'
import {useStoreLocator} from './use-store-locator'
import {useExtensionStore} from '../hooks/use-extension-store'
import PropTypes from 'prop-types'

// Mock the hook
jest.mock('./use-store-locator', () => ({
useStoreLocator: jest.fn()
}))

jest.mock('../hooks/use-extension-store', () => ({
useExtensionStore: jest.fn()
}))

// Mock the StoreLocatorModal component
jest.mock('./modal', () => ({
// eslint-disable-next-line react/prop-types
StoreLocatorModal: ({isOpen, onClose}) =>
isOpen ? (
<div data-testid="store-locator-modal">
Modal Content
<button onClick={onClose}>Close</button>
</div>
) : null
}))

describe('withStoreLocator', () => {
const mockConfig = {
defaultCountryCode: 'US',
Expand All @@ -25,12 +41,18 @@ describe('withStoreLocator', () => {
supportedCountries: []
}

const mockStore = {
isModalOpen: false,
closeModal: jest.fn()
}

beforeEach(() => {
useStoreLocator.mockReturnValue({
searchStoresParams: {},
setSearchStoresParams: jest.fn(),
config: mockConfig
})
useExtensionStore.mockReturnValue(mockStore)
})

it('wraps component with StoreLocatorProvider', () => {
Expand Down Expand Up @@ -67,4 +89,47 @@ describe('withStoreLocator', () => {

expect(WrappedComponent.displayName).toBe('WithStoreLocator(TestComponent)')
})

it('renders modal when isModalOpen is true', () => {
const TestComponent = () => <div>Test Component</div>
const WrappedComponent = withStoreLocator(TestComponent, mockConfig)

useExtensionStore.mockReturnValue({
...mockStore,
isModalOpen: true
})

render(<WrappedComponent />)
expect(screen.getByTestId('store-locator-modal')).toBeTruthy()
})

it('does not render modal when isModalOpen is false', () => {
const TestComponent = () => <div>Test Component</div>
const WrappedComponent = withStoreLocator(TestComponent, mockConfig)

useExtensionStore.mockReturnValue({
...mockStore,
isModalOpen: false
})

render(<WrappedComponent />)
expect(screen.queryByTestId('store-locator-modal')).toBeNull()
})

it('calls closeModal when modal is closed', () => {
const TestComponent = () => <div>Test Component</div>
const WrappedComponent = withStoreLocator(TestComponent, mockConfig)
const mockCloseModal = jest.fn()

useExtensionStore.mockReturnValue({
isModalOpen: true,
closeModal: mockCloseModal
})

render(<WrappedComponent />)
const closeButton = screen.getByText('Close')
closeButton.click()

expect(mockCloseModal).toHaveBeenCalled()
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
* 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 {useExtensionStore} from '../hooks/use-extension-store'
import {Config as StoreLocatorConfig} from '../types/config'
import {StoreLocatorProvider} from './provider'
import {StoreLocatorModal} from './modal'

/**
* Higher-order component that wraps a component with the StoreLocatorProvider
Expand All @@ -18,9 +20,12 @@ export const withStoreLocator = <P extends object>(
config: StoreLocatorConfig
): React.ComponentType<P> => {
const WithConfig = (props: P) => {
const {isModalOpen, closeModal} = useExtensionStore()

return (
<StoreLocatorProvider config={config}>
<WrappedComponent {...props} />
<StoreLocatorModal isOpen={isModalOpen} onClose={closeModal} />
</StoreLocatorProvider>
)
}
Expand Down
30 changes: 15 additions & 15 deletions packages/extension-chakra-store-locator/src/setup-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,20 @@ import StoreLocatorPage from './pages/store-locator'
import {logger} from './logger'
import extensionMeta from '../extension-meta.json'

// NOTE: Hey Kevin, this is where you are going to define the type of the store slice for your extension. I imagine that you'll
// have something that manages the modal being open/closed here.
interface StoreSlice {
count: number
increment: () => void
decrement: () => void
isModalOpen: boolean
openModal: () => void
closeModal: () => void
}

// This is the store slice definition that we are adding via the `withApplicationExtensionStore` HOC below in the extendApp
// method.
const storeSliceInitializer: SliceInitializer<StoreSlice> = (set) => ({
count: 0,
increment: () => set((state) => ({count: state.count + 1})),
decrement: () => set((state) => ({count: state.count - 1}))
isModalOpen: false,
openModal: () => {
set((state) => ({...state, isModalOpen: true}))
},
closeModal: () => {
set((state) => ({...state, isModalOpen: false}))
}
})
class StoreLocatorExtension extends ApplicationExtension<Config> {
static readonly id = extensionMeta.id
Expand All @@ -57,15 +57,15 @@ class StoreLocatorExtension extends ApplicationExtension<Config> {
}

const HOCs = [
(component: React.ComponentType<any>) => withStoreLocator(component, config),
(component: React.ComponentType<any>) =>
withOptionalCommerceSdkReactProvider(component, config),
(component: React.ComponentType<any>) => withOptionalChakra(component),
(component: React.ComponentType<any>) =>
withApplicationExtensionStore(component, {
id: extensionMeta.id,
initializer: storeSliceInitializer
})
}),
(component: React.ComponentType<any>) => withStoreLocator(component, config),
(component: React.ComponentType<any>) =>
withOptionalCommerceSdkReactProvider(component, config),
(component: React.ComponentType<any>) => withOptionalChakra(component)
]

return applyHOCs(App, HOCs)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ import {
useMediaQuery
} from '@chakra-ui/react'
import {AuthHelpers, useAuthHelper, useCustomerType} from '@salesforce/commerce-sdk-react'
import {
useApplicationExtension,
useApplicationExtensionsStore
} from '@salesforce/pwa-kit-extension-sdk/react'

import {useCurrentBasket} from '../../hooks/use-current-basket'

Expand All @@ -41,7 +45,8 @@ import {
HamburgerIcon,
ChevronDownIcon,
HeartIcon,
SignoutIcon
SignoutIcon,
StoreIcon
} from '../../components/icons'

import {navLinks, messages} from '../../pages/account/constant'
Expand Down Expand Up @@ -123,6 +128,13 @@ const Header = ({
onOpen: onAccountMenuOpen
} = useDisclosure()
const [isDesktop] = useMediaQuery('(min-width: 992px)')
const storeLocatorExtension = useApplicationExtension(
'@salesforce/extension-chakra-store-locator'
)
const isStoreLocatorEnabled = !!storeLocatorExtension && storeLocatorExtension.isEnabled
const {openModal} = useApplicationExtensionsStore((state) => {
return state.state['@salesforce/extension-chakra-store-locator'] || {}
})

const [showLoading, setShowLoading] = useState(false)
// tracking if users enter the popover Content,
Expand Down Expand Up @@ -304,6 +316,20 @@ const Header = ({
{...styles.wishlistIcon}
onClick={onWishlistClick}
/>
{isStoreLocatorEnabled && (
<IconButton
aria-label={intl.formatMessage({
defaultMessage: 'Store Locator',
id: 'header.button.assistive_msg.store_locator'
})}
icon={<StoreIcon />}
{...styles.icons}
variant="unstyled"
onClick={() => {
openModal()
}}
/>
)}
<IconButton
aria-label={intl.formatMessage(
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ jest.mock('@chakra-ui/react', () => {
useMediaQuery: jest.fn().mockReturnValue([true])
}
})

jest.mock('@salesforce/pwa-kit-extension-sdk/react', () => ({
...jest.requireActual('@salesforce/pwa-kit-extension-sdk/react'),
useApplicationExtensionsStore: jest.fn().mockReturnValue({
isModalOpen: false,
closeModal: jest.fn()
})
}))

const MockedComponent = ({history}) => {
const onAccountClick = () => {
history.push(createPathWithDefaults('/account'))
Expand Down

0 comments on commit 3289701

Please sign in to comment.