diff --git a/cypress/components/global-header/global-header.cy.tsx b/cypress/components/global-header/global-header.cy.tsx
index 576a7c0a34..7f9ee0a3a0 100644
--- a/cypress/components/global-header/global-header.cy.tsx
+++ b/cypress/components/global-header/global-header.cy.tsx
@@ -1,7 +1,10 @@
/* eslint-disable jest/valid-expect, jest/valid-expect-in-promise */
import React from "react";
import GlobalHeader from "../../../src/components/global-header";
-import { FullMenuExample } from "../../../src/components/global-header/global-header-test.stories";
+import {
+ FullMenuExample,
+ GlobalHeaderWithErrorHandler,
+} from "../../../src/components/global-header/global-header-test.stories";
import CypressMountWithProviders from "../../support/component-helper/cypress-mount";
import carbonLogo from "../../../logo/carbon-logo.png";
@@ -9,6 +12,12 @@ import navigationBar from "../../locators/navigation-bar";
import { globalHeader, globalHeaderLogo } from "../../locators/global-header";
context("Testing Global Header component", () => {
+ it("should not cause a ResizeObserver-related error to occur", () => {
+ CypressMountWithProviders();
+ cy.wait(500);
+ cy.get("#error-div").should("have.text", "");
+ });
+
it("should check that z-index of component is greater than that of NavigationBar", () => {
CypressMountWithProviders();
globalHeader().invoke("css", "zIndex").as("globalHeaderZIndex");
diff --git a/cypress/components/menu/menu.cy.tsx b/cypress/components/menu/menu.cy.tsx
index dd9854e6ca..134a3394d8 100644
--- a/cypress/components/menu/menu.cy.tsx
+++ b/cypress/components/menu/menu.cy.tsx
@@ -63,6 +63,7 @@ import {
MenuDividerComponent,
InGlobalHeaderStory,
} from "../../../src/components/menu/menu-test.stories";
+import { NavigationBarWithSubmenuAndChangingHeight } from "../../../src/components/navigation-bar/navigation-bar-test.stories";
const span = "span";
const div = "div";
@@ -1960,7 +1961,7 @@ context("Testing Menu component", () => {
});
});
- describe("when inside a GlobalHeader", () => {
+ describe("when inside a Navigation Bar", () => {
it("all the content of a long submenu can be accessed with the keyboard while remaining visible", () => {
CypressMountWithProviders();
@@ -1978,5 +1979,32 @@ context("Testing Menu component", () => {
'[data-component="submenu-wrapper"] ul > li:nth-child(20)'
);
});
+
+ it("all the content of a long submenu can be accessed with the keyboard while remaining visible if the navbar height changes", () => {
+ CypressMountWithProviders();
+
+ cy.viewport(1000, 500);
+
+ menuComponent(1).trigger("keydown", keyCode("downarrow"));
+ submenuItem(1).should("have.length", 21);
+
+ // navigate to "change height" item and press it
+ for (let i = 0; i < 3; i++) {
+ cy.focused().trigger("keydown", keyCode("downarrow"));
+ }
+ cy.focused().trigger("keydown", keyCode("Enter"));
+
+ // reopen menu and scroll to bottom with keyboard
+ cy.wait(100);
+ menuComponent(1).trigger("keydown", keyCode("downarrow"));
+
+ for (let i = 0; i < 21; i++) {
+ cy.focused().trigger("keydown", keyCode("downarrow"));
+ }
+
+ cy.checkInViewport(
+ '[data-component="submenu-wrapper"] ul > li:nth-child(21)'
+ );
+ });
});
});
diff --git a/src/components/global-header/global-header-test.stories.tsx b/src/components/global-header/global-header-test.stories.tsx
index a684e6955b..bcff7fd3a5 100644
--- a/src/components/global-header/global-header-test.stories.tsx
+++ b/src/components/global-header/global-header-test.stories.tsx
@@ -1,7 +1,7 @@
-import React from "react";
+import React, { useState, useEffect } from "react";
import { ComponentMeta, ComponentStory } from "@storybook/react";
-import GlobalHeader from "./global-header.component";
+import GlobalHeader, { GlobalHeaderProps } from "./global-header.component";
import { Menu, MenuItem, MenuDivider } from "../menu";
import VerticalDivider from "../vertical-divider";
import NavigationBar from "../navigation-bar";
@@ -96,3 +96,23 @@ export const FullMenuExample = () => (
>
);
+
+export const GlobalHeaderWithErrorHandler = ({
+ ...props
+}: GlobalHeaderProps) => {
+ const [error, setError] = useState("");
+ useEffect(() => {
+ const handleError = (e: ErrorEvent) => {
+ setError(e.message);
+ };
+ window.addEventListener("error", handleError);
+
+ return () => window.removeEventListener("error", handleError);
+ });
+ return (
+ <>
+
+
{error}
+ >
+ );
+};
diff --git a/src/components/global-header/global-header.spec.tsx b/src/components/global-header/global-header.spec.tsx
index 060465d45f..6b5e2839e7 100644
--- a/src/components/global-header/global-header.spec.tsx
+++ b/src/components/global-header/global-header.spec.tsx
@@ -1,24 +1,12 @@
import { render, screen } from "@testing-library/react";
import React from "react";
import GlobalHeader, { GlobalHeaderProps } from "./global-header.component";
-import Logger from "../../__internal__/utils/logger";
-
-// mock Logger.deprecate so that no console warnings occur while running the tests
-const loggerSpy = jest.spyOn(Logger, "deprecate");
function renderer(props?: GlobalHeaderProps) {
return render(foobar);
}
describe("Global Header", () => {
- beforeAll(() => {
- loggerSpy.mockImplementation(() => {});
- });
-
- afterAll(() => {
- loggerSpy.mockRestore();
- });
-
it("should be visible with correct accessible name", () => {
renderer();
expect(screen.getByRole("navigation")).toHaveAccessibleName(
diff --git a/src/components/navigation-bar/components.test-pw.tsx b/src/components/navigation-bar/components.test-pw.tsx
index e8aa9dd791..505a54f963 100644
--- a/src/components/navigation-bar/components.test-pw.tsx
+++ b/src/components/navigation-bar/components.test-pw.tsx
@@ -1,4 +1,4 @@
-import React from "react";
+import React, { useState, useEffect } from "react";
import { ComponentStory } from "@storybook/react";
import NavigationBar, { NavigationBarProps } from ".";
import { Menu, MenuDivider, MenuItem } from "../menu";
@@ -218,3 +218,23 @@ Fixed.parameters = {
docs: { inlineStories: false, iframeHeight: 200 },
themeProvider: { chromatic: { theme: "sage" } },
};
+
+export const NavigationBarWithErrorHandler = ({
+ ...props
+}: NavigationBarProps) => {
+ const [error, setError] = useState("");
+ useEffect(() => {
+ const handleError = (e: ErrorEvent) => {
+ setError(e.message);
+ };
+ window.addEventListener("error", handleError);
+
+ return () => window.removeEventListener("error", handleError);
+ });
+ return (
+ <>
+
+ {error}
+ >
+ );
+};
diff --git a/src/components/navigation-bar/fixed-navigation-bar-context.spec.tsx b/src/components/navigation-bar/fixed-navigation-bar-context.spec.tsx
index 78842c449d..5ae4e9194b 100644
--- a/src/components/navigation-bar/fixed-navigation-bar-context.spec.tsx
+++ b/src/components/navigation-bar/fixed-navigation-bar-context.spec.tsx
@@ -14,15 +14,13 @@ const ConsumerComponent = () => {
};
const mockNavbarElement = { offsetHeight: 40 } as HTMLElement;
+const navbarRef = { current: mockNavbarElement };
const MockComponent = (
- props: Omit
+ props: Omit
) => {
return (
-
+
);
diff --git a/src/components/navigation-bar/fixed-navigation-bar.context.tsx b/src/components/navigation-bar/fixed-navigation-bar.context.tsx
index 6f62931f00..d47c3d60b9 100644
--- a/src/components/navigation-bar/fixed-navigation-bar.context.tsx
+++ b/src/components/navigation-bar/fixed-navigation-bar.context.tsx
@@ -1,4 +1,4 @@
-import React, { createContext, useState, useCallback } from "react";
+import React, { createContext, useState, useCallback, useEffect } from "react";
import useResizeObserver from "../../hooks/__internal__/useResizeObserver/useResizeObserver";
import { NavigationBarProps } from ".";
@@ -15,7 +15,7 @@ export interface FixedNavigationBarContextProviderProps
NavigationBarProps,
"position" | "orientation" | "offset" | "children"
> {
- navbarElement: HTMLElement | null;
+ navbarRef: React.RefObject;
}
export const FixedNavigationBarContextProvider = ({
@@ -23,16 +23,22 @@ export const FixedNavigationBarContextProvider = ({
orientation,
offset,
children,
- navbarElement,
+ navbarRef,
}: FixedNavigationBarContextProviderProps) => {
- const [navbarHeight, setNavbarHeight] = useState(navbarElement?.offsetHeight);
+ const [navbarHeight, setNavbarHeight] = useState(
+ navbarRef.current?.offsetHeight
+ );
const updateHeight = useCallback(
- () => setNavbarHeight(navbarElement?.offsetHeight),
- [navbarElement]
+ () => setNavbarHeight(navbarRef.current?.offsetHeight),
+ [navbarRef]
);
- useResizeObserver({ current: navbarElement }, updateHeight);
+ useEffect(() => {
+ updateHeight();
+ }, [updateHeight]);
+
+ useResizeObserver(navbarRef, updateHeight);
let submenuMaxHeight;
diff --git a/src/components/navigation-bar/navigation-bar-test.stories.tsx b/src/components/navigation-bar/navigation-bar-test.stories.tsx
index 4060dfb53e..912e043816 100644
--- a/src/components/navigation-bar/navigation-bar-test.stories.tsx
+++ b/src/components/navigation-bar/navigation-bar-test.stories.tsx
@@ -1,9 +1,10 @@
-import React from "react";
+import React, { useRef } from "react";
import NavigationBar, { NavigationBarProps } from ".";
+import { Menu, MenuItem } from "../menu";
export default {
title: "Navigation Bar/Test",
- includeStories: ["DefaultStory"],
+ includeStories: ["DefaultStory", "NavigationBarWithSubmenuAndChangingHeight"],
parameters: {
info: { disable: true },
chromatic: {
@@ -25,3 +26,45 @@ DefaultStory.args = {
position: undefined,
offset: "0",
};
+
+export const NavigationBarWithSubmenuAndChangingHeight = () => {
+ const wrapperRef = useRef(null);
+ const toggleHeight = () => {
+ const navbarElement = wrapperRef.current?.querySelector("nav");
+ if (navbarElement) {
+ navbarElement.style.height =
+ navbarElement.style.height === "100px" ? "40px" : "100px";
+ }
+ };
+ return (
+
+
+
+
+
+ );
+};
diff --git a/src/components/navigation-bar/navigation-bar.component.tsx b/src/components/navigation-bar/navigation-bar.component.tsx
index 86397ce734..9a795441cc 100644
--- a/src/components/navigation-bar/navigation-bar.component.tsx
+++ b/src/components/navigation-bar/navigation-bar.component.tsx
@@ -1,4 +1,4 @@
-import React, { useState } from "react";
+import React, { useRef } from "react";
import { PaddingProps, FlexboxProps } from "styled-system";
import StyledNavigationBar from "./navigation-bar.style";
import { FixedNavigationBarContextProvider } from "./fixed-navigation-bar.context";
@@ -32,35 +32,35 @@ export const NavigationBar = ({
children,
ariaLabel,
position,
- offset = "0",
+ offset = "0px",
orientation,
isGlobal,
...props
}: NavigationBarProps): JSX.Element => {
- const [navbarElement, setNavbarElement] = useState(null);
+ const navbarRef = useRef(null);
return (
-
-
{!isLoading && children}
-
-
+
+
);
};
diff --git a/src/components/navigation-bar/navigation-bar.pw.tsx b/src/components/navigation-bar/navigation-bar.pw.tsx
index 65c7513314..7724f16a68 100644
--- a/src/components/navigation-bar/navigation-bar.pw.tsx
+++ b/src/components/navigation-bar/navigation-bar.pw.tsx
@@ -15,6 +15,7 @@ import {
ContentMaxWidthBox,
Sticky,
Fixed,
+ NavigationBarWithErrorHandler,
} from "./components.test-pw";
import navigationBar from "../../../playwright/components/navigation-bar";
@@ -32,6 +33,15 @@ const variants = [
const offsetVal = [25, 100, -100];
test.describe("Test props for NavigationBar component", () => {
+ test("should not cause a ResizeObserver-related error to occur", async ({
+ mount,
+ page,
+ }) => {
+ await mount();
+
+ await expect(page.locator("#error-div")).toContainText("");
+ });
+
specialCharacters.forEach((childrenValue) => {
test(`should render with ${childrenValue} as a children`, async ({
mount,
diff --git a/src/components/navigation-bar/navigation-bar.spec.tsx b/src/components/navigation-bar/navigation-bar.spec.tsx
index 79fcbc7243..02fd159dd5 100644
--- a/src/components/navigation-bar/navigation-bar.spec.tsx
+++ b/src/components/navigation-bar/navigation-bar.spec.tsx
@@ -52,7 +52,9 @@ describe("NavigationBar", () => {
);
- expect(wrapper.prop("data-component")).toBe("navigation-bar");
+ expect(wrapper.find(StyledNavigationBar).prop("data-component")).toBe(
+ "navigation-bar"
+ );
});
it("should provide ariaLabel correctly", () => {
@@ -62,7 +64,9 @@ describe("NavigationBar", () => {
);
- expect(wrapper.prop("aria-label")).toBe("my aria label");
+ expect(wrapper.find(StyledNavigationBar).prop("aria-label")).toBe(
+ "my aria label"
+ );
});
it("should render `light` scheme as default", () => {
@@ -72,7 +76,9 @@ describe("NavigationBar", () => {
);
- expect(wrapper.props().navigationType).toBe("light");
+ expect(wrapper.find(StyledNavigationBar).props().navigationType).toBe(
+ "light"
+ );
});
it("should render correct styles in `light` scheme", () => {
@@ -190,7 +196,7 @@ describe("NavigationBar", () => {
assertStyleMatch(
{
position: `${position}`,
- [orientation]: offset || "0",
+ [orientation]: offset || "0px",
...(position === "fixed" && {
width: "100%",
boxSizing: "border-box",