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
3 changes: 2 additions & 1 deletion packages/extension-chakra-storefront/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ module.exports = {
createTestGlob('components/icons'),
createTestGlob('components/image-gallery'),
createTestGlob('components/drawer-menu'),
createTestGlob('components/header'),
createTestGlob('components/links-list'),
createTestGlob('components/locale-selector'),
createTestGlob('components/nested-accordion'),
Expand Down Expand Up @@ -61,7 +62,7 @@ module.exports = {
'<rootDir>/src/hooks/use-toast.test.js',
'<rootDir>/src/pages/product-detail/metadata.test.js',
'<rootDir>/src/pages/product-list/metadata.test.js',
'<rootDir>/src/hooks/use-toast.test.js',
'<rootDir>/src/hooks/use-toast.test.js'
// '<rootDir>/src/hooks/use-auth-modal.test.js' // TODO: enable after Account page has been migrated
],
moduleNameMapper: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,16 +199,22 @@ const DrawerMenu = ({
}
itemsAfter={({depth}) =>
depth === 1 && (
<Button variant="unstyled" onClick={onSignoutClick}>
<Flex align={'center'}>
<SignoutIcon boxSize={5} />
<Text as="span">
{intl.formatMessage({
id: 'drawer_menu.button.log_out',
defaultMessage: 'Log Out'
})}
</Text>
</Flex>
<Button
variant="ghost"
css={styles.signoutButton}
onClick={onSignoutClick}
>
<SignoutIcon
aria-hidden={true}
boxSize={5}
css={styles.signoutIcon}
/>
<Text css={styles.signoutText} as="span">
{intl.formatMessage({
id: 'drawer_menu.button.log_out',
defaultMessage: 'Log Out'
})}
</Text>
</Button>
)
}
Expand Down
339 changes: 172 additions & 167 deletions packages/extension-chakra-storefront/src/components/header/index.jsx

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
*/
import React from 'react'
import PropTypes from 'prop-types'
import userEvent from '@testing-library/user-event'
import {fireEvent, screen, waitFor, act} from '@testing-library/react'
import Header from '../../components/header/index'
import {renderWithProviders, createPathWithDefaults} from '../../utils/test-utils'
Expand Down Expand Up @@ -57,23 +56,26 @@ beforeEach(() => {
)
})
afterEach(() => {
jest.restoreAllMocks()
localStorage.clear()
})

test('renders Header', async () => {
renderWithProviders(<Header />)

await waitFor(() => {
const menu = screen.getByLabelText('Menu')
const logo = screen.getByLabelText('Logo')
const account = screen.getByLabelText(/my account/i)
const cart = screen.getByLabelText('My cart, number of items: 0')
const wishlist = screen.getByLabelText('Wishlist')
// header is rendering registered user
const account = screen.getByLabelText(/Open account menu/i)
const cart = screen.getByLabelText('My cart, number of items: 2')
const searchInput = document.querySelector('input[type="search"]')
expect(menu).toBeInTheDocument()
expect(logo).toBeInTheDocument()
expect(account).toBeInTheDocument()
expect(cart).toBeInTheDocument()
expect(wishlist).toBeInTheDocument()
// Note: Wishlist button is currently commented out in the header component
// expect(wishlist).toBeInTheDocument()
expect(searchInput).toBeInTheDocument()
})
})
Expand All @@ -91,21 +93,43 @@ test('renders Header with event handlers', async () => {
onMyCartClick={onMyCartClick}
/>
)
// wait til the component is properly rendered before performing any action
await waitFor(() => {
const menu = screen.getByLabelText('Menu')
const logo = screen.getByLabelText('Logo')
const account = screen.getByLabelText(/my account/i)
const cart = screen.getByLabelText('My cart, number of items: 0')
expect(menu).toBeInTheDocument()
expect(screen.getByLabelText('Menu')).toBeInTheDocument()
expect(screen.getByLabelText('Logo')).toBeInTheDocument()
expect(screen.getByLabelText(/Open account menu/i)).toBeInTheDocument()
expect(screen.getByLabelText(/My cart, number of items: 2/i)).toBeInTheDocument()
})
const menu = screen.getByLabelText('Menu')
const logo = screen.getByLabelText('Logo')
const account = screen.getByLabelText(/Open account menu/i)
const cart = screen.getByLabelText(/My cart, number of items: 2/)

await act(async () => {
fireEvent.click(menu)
expect(onMenuClick).toHaveBeenCalledTimes(1)
})
expect(onMenuClick).toHaveBeenCalledTimes(1)

await act(async () => {
fireEvent.click(logo)
expect(onLogoClick).toHaveBeenCalledTimes(1)
fireEvent.click(account)
expect(onMyAccountClick).toHaveBeenCalledTimes(1)
})
expect(onLogoClick).toHaveBeenCalledTimes(1)

await act(async () => {
fireEvent.click(cart)
expect(onMyCartClick).toHaveBeenCalledTimes(1)
})
expect(onMyCartClick).toHaveBeenCalledTimes(1)

await act(async () => {
fireEvent.mouseEnter(account)
})
// The onClick handler is on the AccountIcon inside the button,
// so we need to find and click that
const accountIcon = account.querySelector('svg[aria-label="account"]')
await act(async () => {
fireEvent.click(accountIcon)
})
expect(onMyAccountClick).toHaveBeenCalledTimes(1)
})

/**
Expand Down Expand Up @@ -149,73 +173,90 @@ test('renders cart badge when basket is loaded', async () => {

test('route to account page when an authenticated users click on account icon', async () => {
const history = createMemoryHistory()
// mock push function
history.push = jest.fn()
renderWithProviders(<MockedComponent history={history} />)

await waitFor(() => {
// Look for account icon
const accountTrigger = screen.getByLabelText('Open account menu')
// Look for account button
const accountTrigger = screen.getByLabelText(/Open account menu/)
expect(accountTrigger).toBeInTheDocument()
})
const accountIcon = screen.getByLabelText(/my account/i)
fireEvent.click(accountIcon)

const accountButton = screen.getByLabelText(/Open account menu/)

await act(async () => {
// Use mouseEnter to open the popover, then click on the AccountIcon inside
fireEvent.mouseEnter(accountButton)
})

// The onClick handler is on the AccountIcon inside the button,
// so we need to find and click that
const accountIcon = accountButton.querySelector('svg[aria-label="account"]')
await act(async () => {
fireEvent.click(accountIcon)
})
await waitFor(() => {
expect(history.push).toHaveBeenCalledWith(createPathWithDefaults('/account'))
})

fireEvent.keyDown(accountIcon, {key: 'Enter', code: 'Enter'})
// Test keyDown on the AccountIcon
await act(async () => {
fireEvent.keyDown(accountIcon, {key: 'Enter', code: 'Enter'})
})
await waitFor(() => {
expect(history.push).toHaveBeenCalledWith(createPathWithDefaults('/account'))
})
})

test('route to wishlist page when an authenticated users click on wishlist icon', async () => {
const user = userEvent.setup()
// unskip when wishlist page is converted to chakra v3
test.skip('route to wishlist page when an authenticated users click on wishlist icon', async () => {
const history = createMemoryHistory()
// mock push function
history.push = jest.fn()

renderWithProviders(<MockedComponent history={history} />)
const {user} = renderWithProviders(<MockedComponent history={history} />)

await waitFor(() => {
// Look for account icon
const accountTrigger = screen.getByLabelText('Open account menu')
expect(accountTrigger).toBeInTheDocument()
})
const wishlistIcon = screen.getByRole('button', {name: /wishlist/i})
await user.click(wishlistIcon)
await act(async () => {
await user.click(wishlistIcon)
})
await waitFor(() => {
expect(history.push).toHaveBeenCalledWith(createPathWithDefaults('/account/wishlist'))
})
})

test('shows dropdown menu when an authenticated users hover on the account icon', async () => {
const user = userEvent.setup()
global.server.use(
rest.post('*/customers/action/login', (req, res, ctx) => {
return res(ctx.delay(0), ctx.status(200), ctx.json(mockedRegisteredCustomer))
})
)
const history = createMemoryHistory()
// mock push function
history.push = jest.fn()

await act(async () => {
renderWithProviders(<MockedComponent history={history} />)
})

await waitFor(() => {
// Look for account icon
const accountTrigger = screen.getByLabelText('Open account menu')
// Look for account button
const accountTrigger = screen.getByLabelText(/Open account menu/i)
expect(accountTrigger).toBeInTheDocument()
})
const accountIcon = screen.getByLabelText(/my account/i)
fireEvent.click(accountIcon)
await waitFor(() => {
expect(history.push).toHaveBeenCalledWith(createPathWithDefaults('/account'))

const accountButton = screen.getByLabelText(/Open account menu/i)

// Use mouseEnter to open the popover/dropdown
await act(async () => {
fireEvent.mouseEnter(accountButton)
})
await user.hover(accountIcon)

// Now check that the dropdown menu items are visible
await waitFor(() => {
expect(screen.getByText(/account details/i)).toBeInTheDocument()
expect(screen.getByText(/addresses/i)).toBeInTheDocument()
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,38 @@ import {Link as ChakraLink} from '@chakra-ui/react'
import {Link as SPALink, NavLink as NavSPALink} from 'react-router-dom'
import useMultiSite from '../../hooks/use-multi-site'

const Link = React.forwardRef(({href, to, useNavLink = false, ...props}, ref) => {
const Link = React.forwardRef(({href, to, useNavLink = false, css, children, ...props}, ref) => {
const _href = to || href
const {buildUrl} = useMultiSite()
const updatedHref = buildUrl(_href)

const isActive = useNavLink
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Chakra link no longers at active class by default, we need to do it here and restyling the active class manually for account menu in the header

? (_, location) => {
return location.pathname.endsWith(_href)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since we have multi-site, so we only need check the last section of the pathname.

}
: undefined

return (
<ChakraLink
as={useNavLink ? NavSPALink : SPALink}
{...(useNavLink && {exact: true})}
{...props}
to={updatedHref}
ref={ref}
/>
<ChakraLink asChild {...props} css={css} ref={ref}>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Chakra V3 advised to not use as to render component, instead using asChild and render children components

{useNavLink ? (
<NavSPALink to={updatedHref} isActive={isActive}>
{children}
</NavSPALink>
) : (
<SPALink to={updatedHref}>{children}</SPALink>
)}
</ChakraLink>
)
})

Link.displayName = 'Link'

Link.propTypes = {href: PropTypes.string, to: PropTypes.string, useNavLink: PropTypes.bool}
Link.propTypes = {
href: PropTypes.string,
to: PropTypes.string,
useNavLink: PropTypes.bool,
children: PropTypes.node,
css: PropTypes.oneOfType([PropTypes.object, PropTypes.func, PropTypes.array, PropTypes.string])
}

export default React.memo(Link)
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import OfflineBanner from '../offline-banner'
import OfflineBoundary from '../offline-boundary'
import Seo from '../seo'
import ScrollToTop from '../scroll-to-top'
import Fade from '../fade'

// Local Project Hooks
import {AuthModal, useAuthModal} from '../../hooks/use-auth-modal'
Expand All @@ -67,23 +68,23 @@ const PlaceholderComponent: React.FC = () => (
</Center>
)

// const DrawerMenuItemWithData = withCommerceSdkReactHookData(
// ({itemComponent: ItemComponent, data, ...rest}: any) => (
// <Fade in={true}>
// <ItemComponent {...rest} item={data} itemComponent={DrawerMenuItemWithData} />
// </Fade>
// ),
// {
// hook: useCategory,
// queryOptions: ({item}: {item: {id: string}}) => ({
// parameters: {
// id: item.id
// }
// }),
// placeholder: PlaceholderComponent
// }
// )
//
const DrawerMenuItemWithData = withCommerceSdkReactHookData(
({itemComponent: ItemComponent, data, ...rest}: any) => (
<Fade in={true}>
<ItemComponent {...rest} item={data} itemComponent={DrawerMenuItemWithData} />
</Fade>
),
{
hook: useCategory,
queryOptions: ({item}: {item: {id: string}}) => ({
parameters: {
id: item.id
}
}),
placeholder: PlaceholderComponent
}
)

const ListMenuContentWithData = withCommerceSdkReactHookData(
({data, ...rest}: any) => <ListMenuContent {...rest} item={data} />,
{
Expand Down Expand Up @@ -288,17 +289,19 @@ const withLayout = <P extends object>(WrappedComponent: React.ComponentType<P>)
onMyAccountClick={onAccountClick}
onWishlistClick={onWishlistClick}
>
{/* <HideOnDesktop>*/}
{/* <DrawerMenu*/}
{/* isOpen={isOpen}*/}
{/* onClose={onClose}*/}
{/* onLogoClick={onLogoClick}*/}
{/* root={categories?.[CAT_MENU_DEFAULT_ROOT_CATEGORY]}*/}
{/* itemsKey="categories"*/}
{/* itemsCountKey="onlineSubCategoriesCount"*/}
{/* itemComponent={DrawerMenuItemWithData}*/}
{/* />*/}
{/* </HideOnDesktop>*/}
{/* TODO: mobile menu */}
<HideOnDesktop>
<DrawerMenu
isOpen={open}
onClose={onClose}
onLogoClick={onLogoClick}
root={categories?.[CAT_MENU_DEFAULT_ROOT_CATEGORY]}
itemsKey="categories"
itemsCountKey="onlineSubCategoriesCount"
itemComponent={DrawerMenuItemWithData}
/>
</HideOnDesktop>
{/*TODO: Fix menu to load children lazily.*/}
<HideOnMobile>
<ListMenu
root={categories?.[CAT_MENU_DEFAULT_ROOT_CATEGORY]}
Expand Down
Loading
Loading