Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
5 changes: 1 addition & 4 deletions packages/extension-chakra-storefront/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ module.exports = {
createTestGlob('components/swatch-group'),
createTestGlob('components/toaster'),
createTestGlob('components/list-menu'),
createTestGlob('components/skip-nav'),
createTestGlob('components/login'),
createTestGlob('components/register'),
createTestGlob('components/email-confirmation'),
Expand Down Expand Up @@ -86,10 +87,6 @@ module.exports = {
'<rootDir>/node_modules/@chakra-ui/react/dist/cjs/$1/index.cjs',
'<rootDir>/node_modules/@chakra-ui/react/dist/cjs/index.cjs'
],
'^@chakra-ui/skip-nav/(.*)$': [
'<rootDir>/node_modules/@chakra-ui/skip-nav/dist/index.js',
'<rootDir>/node_modules/@chakra-ui/skip-nav/dist/$1.js'
],
'^proxy-compare$': '<rootDir>/node_modules/proxy-compare/dist/cjs/index.js',
'^uqr$': '<rootDir>/node_modules/uqr/dist/index.cjs',
// handle pwa-kit extensibility special import
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/*
* Copyright (c) 2023, 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 {render, screen} from '@testing-library/react'
import {ChakraProvider} from '@chakra-ui/react'
import {SkipNavLink, SkipNavContent} from './index'
import system from '../../theme'

const TestWrapper = ({children}: {children: React.ReactNode}) => (
<ChakraProvider value={system}>{children}</ChakraProvider>
)

describe('SkipNavLink', () => {
test('renders with default props', () => {
render(
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Can we use renderWithProviders?

<TestWrapper>
<SkipNavLink>Skip to Content</SkipNavLink>
</TestWrapper>
)

const link = screen.getByRole('link', {name: 'Skip to Content'})
expect(link).toBeInTheDocument()
expect(link).toHaveAttribute('href', '#skip-to-content')
})

test('renders with custom href', () => {
render(
<TestWrapper>
<SkipNavLink href="#main-content">Skip to Main</SkipNavLink>
</TestWrapper>
)

const link = screen.getByRole('link', {name: 'Skip to Main'})
expect(link).toHaveAttribute('href', '#main-content')
})

test('has correct accessibility attributes', () => {
render(
<TestWrapper>
<SkipNavLink>Skip to Content</SkipNavLink>
</TestWrapper>
)

const link = screen.getByRole('link', {name: 'Skip to Content'})
expect(link).toHaveAttribute('href', '#skip-to-content')
})

test('has visually hidden styles by default', () => {
render(
<TestWrapper>
<SkipNavLink>Skip to Content</SkipNavLink>
</TestWrapper>
)

const link = screen.getByRole('link', {name: 'Skip to Content'})

// The link should be focusable (no aria-hidden) but positioned off-screen
expect(link).not.toHaveAttribute('aria-hidden')
expect(link).toHaveAttribute('href', '#skip-to-content')
})

test('accepts custom zIndex prop', () => {
render(
<TestWrapper>
<SkipNavLink zIndex={9999}>Skip to Content</SkipNavLink>
</TestWrapper>
)

const link = screen.getByRole('link', {name: 'Skip to Content'})
expect(link).toBeInTheDocument()
// Note: Testing the actual zIndex value may require more complex setup
// as it depends on the Chakra theme system
})
})

describe('SkipNavContent', () => {
test('renders with default props', () => {
render(
<TestWrapper>
<SkipNavContent>
<div>Main content</div>
</SkipNavContent>
</TestWrapper>
)

const content = screen.getByText('Main content')
const container = content.parentElement

expect(container).toHaveAttribute('id', 'skip-to-content')
expect(container).toHaveAttribute('tabIndex', '-1')
})

test('renders with custom id', () => {
render(
<TestWrapper>
<SkipNavContent id="main-content">
<div>Main content</div>
</SkipNavContent>
</TestWrapper>
)

const content = screen.getByText('Main content')
const container = content.parentElement

expect(container).toHaveAttribute('id', 'main-content')
})

test('applies custom styles', () => {
const customStyles = {
backgroundColor: 'red',
padding: '10px'
}

render(
<TestWrapper>
<SkipNavContent style={customStyles}>
<div>Main content</div>
</SkipNavContent>
</TestWrapper>
)

const content = screen.getByText('Main content')
const container = content.parentElement

expect(container).toHaveStyle('background-color: red')
expect(container).toHaveStyle('padding: 10px')
})

test('renders children correctly', () => {
render(
<TestWrapper>
<SkipNavContent>
<h1>Main Heading</h1>
<p>Some content</p>
</SkipNavContent>
</TestWrapper>
)

expect(screen.getByText('Main Heading')).toBeInTheDocument()
expect(screen.getByText('Some content')).toBeInTheDocument()
})

test('has correct tabIndex for focus management', () => {
render(
<TestWrapper>
<SkipNavContent>
<div>Main content</div>
</SkipNavContent>
</TestWrapper>
)

const content = screen.getByText('Main content')
const container = content.parentElement

expect(container).toHaveAttribute('tabIndex', '-1')
})
})

describe('SkipNav Integration', () => {
test('link and content work together', () => {
render(
<TestWrapper>
<SkipNavLink href="#main-content">Skip to Content</SkipNavLink>
<SkipNavContent id="main-content">
<div>Main content</div>
</SkipNavContent>
</TestWrapper>
)

const link = screen.getByRole('link', {name: 'Skip to Content'})
const content = screen.getByText('Main content')
const container = content.parentElement

expect(link).toHaveAttribute('href', '#main-content')
expect(container).toHaveAttribute('id', 'main-content')
})

test('uses default ids when not specified', () => {
render(
<TestWrapper>
<SkipNavLink>Skip to Content</SkipNavLink>
<SkipNavContent>
<div>Main content</div>
</SkipNavContent>
</TestWrapper>
)

const link = screen.getByRole('link', {name: 'Skip to Content'})
const content = screen.getByText('Main content')
const container = content.parentElement

expect(link).toHaveAttribute('href', '#skip-to-content')
expect(container).toHaveAttribute('id', 'skip-to-content')
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright (c) 2023, 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 {Box, Link} from '@chakra-ui/react'

interface SkipNavLinkProps {
children: React.ReactNode
zIndex?: string | number
href?: string
}

interface SkipNavContentProps {
children: React.ReactNode
style?: React.CSSProperties
id?: string
}

/**
* SkipNavLink component provides a skip link for keyboard navigation
* with initial state screen reader accessible but visually hidden
*/
export const SkipNavLink: React.FC<SkipNavLinkProps> = ({
children,
zIndex = 'skipLink',
href = '#skip-to-content',
...props
}) => {
return (
<Link
href={href}
position="absolute"
zIndex={zIndex}
css={{
position: 'absolute',
left: '-10000px',
top: 'auto',
width: '1px',
height: '1px',
overflow: 'hidden',
'&:focus, &:focus-visible': {
position: 'fixed !important',
top: '6px !important',
left: '6px !important',
zIndex: 9999,
width: 'auto !important',
height: 'auto !important',
overflow: 'visible !important',
padding: '8px',
backgroundColor: 'white',
color: 'black',
textDecoration: 'none',
border: '2px solid black',
borderRadius: '4px',
fontSize: '14px',
fontWeight: 'bold',
outline: 'none',
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
whiteSpace: 'nowrap'
}
}}
{...props}
>
{children}
</Link>
)
}

/**
* SkipNavContent component provides the target content area for skip navigation
*/
export const SkipNavContent: React.FC<SkipNavContentProps> = ({
children,
style,
id = 'skip-to-content',
...props
}) => {
return (
<Box id={id} style={style} tabIndex={-1} {...props}>
{children}
</Box>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {

// Local Project Components
import {DrawerMenu} from '../drawer-menu'
import {SkipNavLink, SkipNavContent} from '../skip-nav'
import {getPathWithLocale} from '../../utils/url'
import {HideOnDesktop, HideOnMobile} from '../responsive'
import {ListMenu, ListMenuContent} from '../list-menu'
Expand Down Expand Up @@ -276,8 +277,7 @@ const withLayout = <P extends object>(WrappedComponent: React.ComponentType<P>)

<ScrollToTop />
<Box id="app" display="flex" flexDirection="column" flex={1}>
{/*TODO: recreating this component because @chakra-ui/skip-nav does not have V3 version*/}
{/*<SkipNavLink zIndex="skipLink">Skip to Content</SkipNavLink>*/}
<SkipNavLink zIndex="skipLink">Skip to Content</SkipNavLink>
<Box css={styles.headerWrapper}>
{!isCheckout ? (
<>
Expand Down Expand Up @@ -318,28 +318,27 @@ const withLayout = <P extends object>(WrappedComponent: React.ComponentType<P>)
</Box>
{!isOnline && <OfflineBanner />}
<AddToCartModalProvider>
{/*TODO: recreating this component because @chakra-ui/skip-nav does not have V3 version*/}
{/*<SkipNavContent*/}
{/* style={{*/}
{/* display: 'flex',*/}
{/* flexDirection: 'column',*/}
{/* flex: 1,*/}
{/* outline: 0*/}
{/* }}*/}
{/*>*/}
<Box
as="main"
id="app-main"
role="main"
display="flex"
flexDirection="column"
flex="1"
<SkipNavContent
style={{
display: 'flex',
flexDirection: 'column',
flex: 1,
outline: 0
}}
>
<OfflineBoundary isOnline={isOnline}>
<WrappedComponent {...(props as P)} />
</OfflineBoundary>
</Box>
{/*</SkipNavContent>*/}
<Box
as="main"
id="app-main"
role="main"
display="flex"
flexDirection="column"
flex="1"
>
<OfflineBoundary isOnline={isOnline}>
<WrappedComponent {...(props as P)} />
</OfflineBoundary>
</Box>
</SkipNavContent>

{!isCheckout ? <Footer /> : <CheckoutFooter />}
<AuthModal {...(authModal as any)} />
Expand Down
3 changes: 3 additions & 0 deletions packages/extension-chakra-storefront/src/theme/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ export const overrides = defineConfig({
body: `-apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`,
mono: `SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace`
},
zIndex: {
skipLink: 9999
},
breakpoints
},
semanticTokens: {
Expand Down
Loading