Skip to content
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

Store locator extension integration @@W-17273365@@ #2225

Merged
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,15 @@ 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'] || {}
}
) || {}
kevinxh marked this conversation as resolved.
Show resolved Hide resolved

const [showLoading, setShowLoading] = useState(false)
// tracking if users enter the popover Content,
Expand Down Expand Up @@ -304,6 +318,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
Loading