Skip to content

Commit

Permalink
test: add test for ContextualMenuDropdown component
Browse files Browse the repository at this point in the history
Signed-off-by: Mason Hu <mason.hu@canonical.com>
Signed-off-by: Nkeiruka <nkeiruka.whenu@canonical.com>
  • Loading branch information
mas-who authored and Kxiru committed Aug 19, 2024
1 parent eb1b45e commit e22f884
Show file tree
Hide file tree
Showing 2 changed files with 189 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -171,4 +171,180 @@ describe("ContextualMenuDropdown ", () => {
).toBe("right");
});
});

describe("dropdown menu vertical positioning", () => {
const renderMenu = (positionNode: HTMLElement, menuHeight: number) => {
// need to mock overflow property for scrollparent
jest
.spyOn(global, "getComputedStyle")
.mockImplementation((node: Element) => {
if (node.id === "scrollParent") {
return {
overflowY: "auto",
overflowX: "auto",
} as CSSStyleDeclaration;
}
return {} as CSSStyleDeclaration;
});

// render the component once to setup initial jsdom elements
const links = Array.from({ length: 10 }).map((_, i) => i);
const { rerender } = render(
<ContextualMenuDropdown
autoAdjust
setAdjustedPosition={jest.fn()}
isOpen
links={links}
positionNode={positionNode}
/>
);

// get the dropdown menu dom element and set its height
// NOTE: we can only do this after the component has been rendered at least once
let dropdownNode = document.querySelector(
".p-contextual-menu__dropdown"
) as HTMLElement;

dropdownNode.getBoundingClientRect = jest
.fn()
.mockReturnValue({ height: menuHeight });

rerender(
<ContextualMenuDropdown
autoAdjust
setAdjustedPosition={jest.fn()}
isOpen
links={links}
positionNode={positionNode}
/>
);
};

const assertMenuPosition = async (position: "above" | "below") => {
await waitFor(() => {
const dropdownNode = document.querySelector(
".p-contextual-menu__dropdown"
) as HTMLElement;

let condition = expect(dropdownNode);
if (position === "below") {
condition = condition.not as jest.JestMatchers<HTMLElement>;
}

condition.toHaveAttribute("style", "bottom: 0px;");
});
};

it("places menu below toggle if there is enough space in window and scroll parent", async () => {
global.innerHeight = 1000;

const scrollParent = document.createElement("div");
jest.spyOn(scrollParent, "getBoundingClientRect").mockReturnValue({
top: 0,
bottom: 0,
height: 1000,
} as DOMRect);
scrollParent.id = "scrollParent";

const positionNode = document.createElement("div");
jest.spyOn(positionNode, "getBoundingClientRect").mockReturnValue({
top: 500,
bottom: 480,
height: 20,
} as DOMRect);

scrollParent.appendChild(positionNode);
document.body.appendChild(scrollParent);

const menuHeight = 200;
renderMenu(positionNode, menuHeight);

await assertMenuPosition("below");

positionNode.remove();
});

it("places menu below toggle if there is not enough space in window or scroll parent, but there is more space below than above", async () => {
global.innerHeight = 600;

const scrollParent = document.createElement("div");
jest.spyOn(scrollParent, "getBoundingClientRect").mockReturnValue({
top: 180,
bottom: 400,
height: 20,
} as DOMRect);
scrollParent.id = "scrollParent";

const positionNode = document.createElement("div");
jest.spyOn(positionNode, "getBoundingClientRect").mockReturnValue({
top: 100,
bottom: 480,
height: 20,
} as DOMRect);

scrollParent.appendChild(positionNode);
document.body.appendChild(scrollParent);

const menuHeight = 200;
renderMenu(positionNode, menuHeight);

await assertMenuPosition("below");

positionNode.remove();
});

it("places menu above if there is not enough space in the window", async () => {
global.innerHeight = 10;

const positionNode = document.createElement("div");
jest.spyOn(positionNode, "getBoundingClientRect").mockReturnValue({
top: 0,
bottom: 50,
height: 50,
} as DOMRect);

jest.spyOn(document.body, "getBoundingClientRect").mockReturnValue({
height: 10,
bottom: 0,
top: 0,
} as DOMRect);
document.body.appendChild(positionNode);

const menuHeight = 200;
renderMenu(positionNode, menuHeight);

await assertMenuPosition("above");

positionNode.remove();
});

it("places menu above if there is enough space in the window, but not enough space in the scroll parent", async () => {
global.innerHeight = 500;

const scrollParent = document.createElement("div");
jest.spyOn(scrollParent, "getBoundingClientRect").mockReturnValue({
top: 0,
bottom: 0,
height: 300,
} as DOMRect);
scrollParent.id = "scrollParent";

const positionNode = document.createElement("div");
jest.spyOn(positionNode, "getBoundingClientRect").mockReturnValue({
top: 200,
bottom: 400,
height: 20,
} as DOMRect);

scrollParent.appendChild(positionNode);
document.body.appendChild(scrollParent);

const menuHeight = 200;
renderMenu(positionNode, menuHeight);

await assertMenuPosition("above");

positionNode.remove();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const getPositionStyle = (
position: Position,
verticalPosition: VerticalPosition,
positionCoords: Props["positionCoords"],
constrainPanelWidth: Props["constrainPanelWidth"],
constrainPanelWidth: Props["constrainPanelWidth"]
): React.CSSProperties => {
if (!positionCoords) {
return null;
Expand Down Expand Up @@ -101,7 +101,7 @@ const getPositionStyle = (
*/
export const adjustForWindow = (
position: Position,
fitsWindow: WindowFitment,
fitsWindow: WindowFitment
): Position => {
let newPosition: string = position;
if (!fitsWindow.fromRight.fitsLeft && newPosition === "right") {
Expand Down Expand Up @@ -144,7 +144,7 @@ export const adjustForWindow = (
const generateLink = <L,>(
link: ButtonProps,
key: React.Key,
handleClose: Props["handleClose"],
handleClose: Props["handleClose"]
) => {
const { children, className, onClick, ...props } = link;
return (
Expand All @@ -167,7 +167,7 @@ const generateLink = <L,>(
};

const getClosestScrollableParent = (
node: HTMLElement | null,
node: HTMLElement | null
): HTMLElement | null => {
let currentNode = node;
while (currentNode && currentNode !== document.body) {
Expand Down Expand Up @@ -209,8 +209,8 @@ const ContextualMenuDropdown = <L,>({
adjustedPosition,
verticalPosition,
positionCoords,
constrainPanelWidth,
),
constrainPanelWidth
)
);
const [maxHeight, setMaxHeight] = useState<number>();
// Update the styles to position the menu.
Expand All @@ -220,8 +220,8 @@ const ContextualMenuDropdown = <L,>({
adjustedPosition,
verticalPosition,
positionCoords,
constrainPanelWidth,
),
constrainPanelWidth
)
);
}, [adjustedPosition, positionCoords, verticalPosition, constrainPanelWidth]);

Expand All @@ -243,27 +243,20 @@ const ContextualMenuDropdown = <L,>({
bottom: toggleRect.bottom - scrollableParentRect.top,
};

// Calculate the rect in relation to the Window
const windowRect = {
top: window.scrollY,
bottom: window.scrollX,
height: window.innerHeight,
};

const scrollParentSpaceBelow =
scrollableParentRect.height - relativeToScrollParentRect.bottom;
const scrollParentSpaceAbove = relativeToScrollParentRect.top;

const dropdownHeight = dropdown.current.getBoundingClientRect().height ?? 0;

const windowSpaceBelow = windowRect.height - toggleRect.bottom;
const windowSpaceBelow = window.innerHeight - toggleRect.bottom;

setVerticalPosition(
(scrollParentSpaceBelow >= dropdownHeight &&
windowSpaceBelow >= dropdownHeight) ||
windowSpaceBelow > scrollParentSpaceAbove
? "bottom"
: "top",
: "top"
);
}, [positionNode]);

Expand All @@ -284,7 +277,7 @@ const ContextualMenuDropdown = <L,>({
scrollOverflow,
setAdjustedPosition,
updateVerticalPosition,
],
]
);

// Handle adjusting the horizontal position and scrolling of the dropdown so that it remains on screen.
Expand All @@ -293,7 +286,7 @@ const ContextualMenuDropdown = <L,>({
positionNode,
onUpdateWindowFitment,
0,
isOpen && (autoAdjust || scrollOverflow),
isOpen && (autoAdjust || scrollOverflow)
);

// Update the styles when the position changes.
Expand Down Expand Up @@ -335,7 +328,7 @@ const ContextualMenuDropdown = <L,>({
return (
<span className="p-contextual-menu__group" key={i}>
{item.map((link, j) =>
generateLink<L>(link, j, handleClose),
generateLink<L>(link, j, handleClose)
)}
</span>
);
Expand Down

0 comments on commit e22f884

Please sign in to comment.