diff --git a/packages/module/src/behavior/withContextMenu.test.tsx b/packages/module/src/behavior/withContextMenu.test.tsx
new file mode 100644
index 00000000..9bb44d8a
--- /dev/null
+++ b/packages/module/src/behavior/withContextMenu.test.tsx
@@ -0,0 +1,44 @@
+import { ReactNode } from 'react';
+import { act, fireEvent, render, screen } from '@testing-library/react';
+
+import { WithContextMenuProps, withContextMenu } from './withContextMenu';
+
+let capturedReference: unknown;
+
+jest.mock('../components/contextmenu/ContextMenu', () => ({
+ __esModule: true,
+ default: ({ children, reference }: { children: ReactNode; reference: unknown }) => {
+ capturedReference = reference;
+ return
{children}
;
+ }
+}));
+
+const WrappedComponent = ({ onContextMenu }: WithContextMenuProps) => (
+
+ Wrapped node
+
+);
+
+describe('withContextMenu', () => {
+ beforeEach(() => {
+ capturedReference = undefined;
+ });
+
+ it('should use viewport coordinates for point-based context menus', async () => {
+ const ComponentWithContextMenu = withContextMenu(() => [Action
])(WrappedComponent);
+
+ render();
+
+ await act(async () => {
+ fireEvent.contextMenu(screen.getByTestId('wrapped-node'), {
+ clientX: 120,
+ clientY: 140,
+ pageX: 420,
+ pageY: 540
+ });
+ });
+
+ expect(await screen.findByTestId('context-menu')).toBeInTheDocument();
+ expect(capturedReference).toEqual({ x: 120, y: 140 });
+ });
+});
diff --git a/packages/module/src/behavior/withContextMenu.tsx b/packages/module/src/behavior/withContextMenu.tsx
index bec89f49..53daa7f7 100644
--- a/packages/module/src/behavior/withContextMenu.tsx
+++ b/packages/module/src/behavior/withContextMenu.tsx
@@ -31,8 +31,8 @@ export const withContextMenu =
setReference(
atPoint
? {
- x: e.pageX,
- y: e.pageY
+ x: e.clientX,
+ y: e.clientY
}
: e.currentTarget
);
diff --git a/packages/module/src/components/contextmenu/ContextMenu.test.tsx b/packages/module/src/components/contextmenu/ContextMenu.test.tsx
new file mode 100644
index 00000000..43fa5eb6
--- /dev/null
+++ b/packages/module/src/components/contextmenu/ContextMenu.test.tsx
@@ -0,0 +1,29 @@
+import { ReactNode } from 'react';
+import { fireEvent, render, screen } from '@testing-library/react';
+
+import ContextMenu from './ContextMenu';
+import { ContextMenuItem } from './index';
+
+jest.mock('../popper/Popper', () => ({
+ __esModule: true,
+ default: ({ children }: { children: ReactNode }) => {children}
+}));
+
+describe('ContextMenu', () => {
+ it('should render menu items inside the topology popper and close on select', () => {
+ const onRequestClose = jest.fn();
+
+ render(
+
+ First
+
+ );
+
+ expect(screen.getByTestId('mock-popper')).toBeInTheDocument();
+
+ const menuItem = screen.getByRole('menuitem', { name: 'First' });
+ fireEvent.click(menuItem);
+
+ expect(onRequestClose).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/packages/module/src/components/contextmenu/ContextMenu.tsx b/packages/module/src/components/contextmenu/ContextMenu.tsx
index ba825446..231b4b86 100644
--- a/packages/module/src/components/contextmenu/ContextMenu.tsx
+++ b/packages/module/src/components/contextmenu/ContextMenu.tsx
@@ -1,5 +1,5 @@
import { useState, useCallback, useEffect } from 'react';
-import { Dropdown } from '@patternfly/react-core';
+import { Menu, MenuContent } from '@patternfly/react-core';
import { css } from '@patternfly/react-styles';
import topologyStyles from '../../css/topology-components';
// FIXME fully qualified due to the effect of long build times on storybook
@@ -35,14 +35,9 @@ const ContextMenu: React.FunctionComponent = ({
return (
- <>>}
- className={css(topologyStyles.topologyContextMenuCDropdownMenu)}
- popperProps={{ appendTo: 'inline' }}
- >
- {children}
-
+
);
};