diff --git a/.all-contributorsrc b/.all-contributorsrc
index a7cd07aaf96d..3088b1880d80 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -1721,6 +1721,13 @@
"name": "Zoë Gathercole",
"avatar_url": "https://avatars.githubusercontent.com/u/56911544?v=4",
"profile": "https://github.com/Zoe-Gathercole",
+ "contributions": ["code"]
+ },
+ {
+ "login": "ashna000",
+ "name": "Ashna Thomas",
+ "avatar_url": "https://avatars.githubusercontent.com/u/12691034?s=96&v=4",
+ "profile": "https://github.com/ashna000",
"contributions": [
"code"
]
diff --git a/README.md b/README.md
index 30d2bfc8a91a..3b7067b4ac91 100644
--- a/README.md
+++ b/README.md
@@ -321,6 +321,9 @@ check out our [Contributing Guide](/.github/CONTRIBUTING.md) and our
Zoë Gathercole 💻 |
Zach Tindall 💻 |
+
+ Ashna Thomas 💻 |
+
diff --git a/packages/react/src/components/UIShell/__tests__/SideNav-test.js b/packages/react/src/components/UIShell/__tests__/SideNav-test.js
index 02d727ae319d..d3974e5300d4 100644
--- a/packages/react/src/components/UIShell/__tests__/SideNav-test.js
+++ b/packages/react/src/components/UIShell/__tests__/SideNav-test.js
@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
-import { render, screen } from '@testing-library/react';
+import { render, screen, fireEvent } from '@testing-library/react';
import React from 'react';
import SideNav from '../SideNav';
@@ -15,8 +15,8 @@ Object.defineProperty(window, 'matchMedia', {
matches: false,
media: query,
onchange: null,
- addListener: jest.fn(), // deprecated
- removeListener: jest.fn(), // deprecated
+ addListener: jest.fn(),
+ removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
@@ -36,6 +36,11 @@ describe('SideNav', () => {
expect(container.childNodes.length).toBe(2);
});
+ it('should not render an overlay if `isFixedNav` is true', () => {
+ const { container } = render();
+ expect(container.childNodes.length).toBe(1);
+ });
+
it('should toggle the overlay-active class when `expanded` is true', () => {
const { container } = render();
expect(container.firstChild).toHaveClass('cds--side-nav__overlay-active');
@@ -64,4 +69,269 @@ describe('SideNav', () => {
render();
expect(ref).toHaveBeenCalledWith(screen.getByRole('navigation'));
});
+
+ it('should call onOverlayClick when overlay is clicked', () => {
+ const onOverlayClick = jest.fn();
+ const { container } = render(
+
+ );
+ const overlay = container.firstChild;
+ fireEvent.click(overlay);
+ expect(onOverlayClick).toHaveBeenCalledTimes(1);
+ });
+
+ it('should not add focus or mouse listeners when disabled', () => {
+ const onToggle = jest.fn();
+ render(
+
+ );
+ const sideNav = screen.getByRole('navigation');
+ fireEvent.focus(sideNav);
+ fireEvent.mouseEnter(sideNav);
+ expect(onToggle).not.toHaveBeenCalled();
+ });
+
+ it('should handle keyboard events like Escape', () => {
+ const onToggle = jest.fn();
+ render(
+
+ );
+ const sideNav = screen.getByRole('navigation');
+ fireEvent.keyDown(sideNav, { key: 'Escape', keyCode: 27 });
+ expect(onToggle).toHaveBeenCalledWith(expect.anything(), false);
+ });
+
+ it('should correctly handle `isRail` when true', () => {
+ render();
+ const sideNav = screen.getByRole('navigation');
+ expect(sideNav).toHaveClass('cds--side-nav--rail');
+ });
+
+ it('should correctly handle `isRail` when false', () => {
+ render();
+ const sideNav = screen.getByRole('navigation');
+ expect(sideNav).not.toHaveClass('cds--side-nav--rail');
+ });
+
+ it('should toggle the expanded state when uncontrolled', () => {
+ const { container } = render();
+ const sideNav = screen.getByRole('navigation');
+ fireEvent.focus(sideNav);
+ expect(container.firstChild).toHaveClass('cds--side-nav__overlay');
+ });
+
+ it('should handle children without throwing error', () => {
+ const { container } = render(
+
+ No Errors Here!
+
+ );
+ expect(container).toBeInTheDocument();
+ });
+
+ it('should pass isSideNavExpanded to Carbon SideNav children', () => {
+ const TestChild = React.forwardRef(({ isSideNavExpanded }, ref) => (
+
+ {isSideNavExpanded ? 'Expanded' : 'Collapsed'}
+
+ ));
+ render(
+
+
+
+ );
+ expect(screen.getByTestId('child')).toHaveTextContent('Collapsed');
+ });
+
+ it('should not pass isSideNavExpanded to non-CarbsideNav children', () => {
+ const NonCarbonChild = () => ;
+ render(
+
+
+
+ );
+ expect(screen.getByTestId('non-carbon-child')).toBeInTheDocument();
+ });
+
+ it('should pass isSideNavExpanded correctly based on controlled state', () => {
+ const TestChild = React.forwardRef(({ isSideNavExpanded }, ref) => (
+
+ {isSideNavExpanded ? 'Expanded' : 'Collapsed'}
+
+ ));
+ const { rerender } = render(
+
+
+
+ );
+ expect(screen.getByTestId('child')).toHaveTextContent('Collapsed');
+ rerender(
+
+
+
+ );
+ expect(screen.getByTestId('child')).toHaveTextContent('Collapsed');
+ });
+
+ it('should call handleToggle and onSideNavBlur when blurred', () => {
+ const onSideNavBlurMock = jest.fn();
+ const TestChild = () => Child
;
+ const { getByRole } = render(
+
+
+
+ );
+ const sideNav = getByRole('navigation');
+ fireEvent.focus(sideNav);
+ fireEvent.blur(sideNav);
+ expect(onSideNavBlurMock).not.toHaveBeenCalled();
+ });
+
+ it('should not call onSideNavBlur if not expanded and isFixedNav is true', () => {
+ const onSideNavBlurMock = jest.fn();
+ const TestChild = () => Child
;
+ const { getByRole } = render(
+
+
+
+ );
+ const sideNav = getByRole('navigation');
+ fireEvent.focus(sideNav);
+ fireEvent.blur(sideNav);
+ expect(onSideNavBlurMock).not.toHaveBeenCalled();
+ });
+
+ it('should call onSideNavBlur when blurred, is not fixed, and is expanded', () => {
+ const onSideNavBlurMock = jest.fn();
+ const TestChild = () => Child
;
+ const { getByRole } = render(
+
+
+
+ );
+ const sideNav = getByRole('navigation');
+ fireEvent.focus(sideNav);
+ const unrelatedElement = document.createElement('div');
+ document.body.appendChild(unrelatedElement);
+ fireEvent.blur(sideNav, { relatedTarget: unrelatedElement });
+ expect(onSideNavBlurMock).toHaveBeenCalled();
+ document.body.removeChild(unrelatedElement);
+ });
+
+ it('should not call onSideNavBlur when isFixedNav is true', () => {
+ const onSideNavBlurMock = jest.fn();
+ const TestChild = () => Child
;
+ const { getByRole } = render(
+
+
+
+ );
+ const sideNav = getByRole('navigation');
+ fireEvent.focus(sideNav);
+ const unrelatedElement = document.createElement('div');
+ document.body.appendChild(unrelatedElement);
+ fireEvent.blur(sideNav, { relatedTarget: unrelatedElement });
+ expect(onSideNavBlurMock).not.toHaveBeenCalled();
+ document.body.removeChild(unrelatedElement);
+ });
+
+ it('should set expanded state to false on mouse leave', () => {
+ const onToggleMock = jest.fn();
+ const TestChild = () => Child
;
+ const { getByRole } = render(
+
+
+
+ );
+ const sideNav = getByRole('navigation');
+ expect(sideNav).toHaveClass('cds--side-nav__navigation');
+ fireEvent.mouseLeave(sideNav);
+ expect(sideNav).toHaveClass('cds--side-nav__navigation');
+ });
+
+ it('should handleToggle if isRail is true', () => {
+ const onToggleMock = jest.fn();
+ const TestChild = () => Child
;
+ const { getByRole } = render(
+
+
+
+ );
+ const sideNav = getByRole('navigation');
+ fireEvent.focus(sideNav);
+ expect(onToggleMock).toHaveBeenCalled();
+ fireEvent.mouseLeave(sideNav);
+ expect(onToggleMock).toHaveBeenCalledWith(false, false);
+ fireEvent.mouseEnter(sideNav);
+ expect(onToggleMock).toHaveBeenCalledWith(true, true);
+ });
+
+ it('should not call handleToggle if isRail is false', () => {
+ const onToggleMock = jest.fn();
+ const TestChild = () => Child
;
+ const { getByRole } = render(
+
+
+
+ );
+ const sideNav = getByRole('navigation');
+ fireEvent.mouseLeave(sideNav);
+ expect(onToggleMock).not.toHaveBeenCalled();
+ });
+
+ it('should expand the SideNav immediately on click', () => {
+ const TestChild = () => Child
;
+ const onToggleMock = jest.fn();
+ const { getByRole } = render(
+
+
+
+ );
+ const sideNav = getByRole('navigation');
+ expect(sideNav).not.toHaveClass('cds--side-nav--expanded');
+ fireEvent.click(sideNav);
+ expect(onToggleMock).toHaveBeenCalledWith(true, true);
+ expect(sideNav).toHaveClass('cds--side-nav--expanded');
+ });
+
+ it('should focus SideNav after tabbing from headerMenuButton', () => {
+ render(
+ <>
+
+
+ >
+ );
+ const mockHeaderMenuButton = screen.getByRole('button');
+ const sideNav = screen.getByRole('navigation');
+
+ mockHeaderMenuButton.focus();
+ fireEvent.keyDown(window, { key: 'Tab' });
+ expect(document.activeElement).toBe(sideNav);
+ });
});