Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 13db1b1

Browse files
authored
Prevent useContextMenu isOpen from being true if the button ref goes away (#9418)
1 parent e1d631c commit 13db1b1

File tree

6 files changed

+38
-35
lines changed

6 files changed

+38
-35
lines changed

src/components/structures/ContextMenu.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -565,8 +565,13 @@ type ContextMenuTuple<T> = [
565565
(val: boolean) => void,
566566
];
567567
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint
568-
export const useContextMenu = <T extends any = HTMLElement>(): ContextMenuTuple<T> => {
569-
const button = useRef<T>(null);
568+
export const useContextMenu = <T extends any = HTMLElement>(inputRef?: RefObject<T>): ContextMenuTuple<T> => {
569+
let button = useRef<T>(null);
570+
if (inputRef) {
571+
// if we are given a ref, use it instead of ours
572+
button = inputRef;
573+
}
574+
570575
const [isOpen, setIsOpen] = useState(false);
571576
const open = (ev?: SyntheticEvent) => {
572577
ev?.preventDefault();
@@ -579,7 +584,7 @@ export const useContextMenu = <T extends any = HTMLElement>(): ContextMenuTuple<
579584
setIsOpen(false);
580585
};
581586

582-
return [isOpen, button, open, close, setIsOpen];
587+
return [button.current ? isOpen : false, button, open, close, setIsOpen];
583588
};
584589

585590
// XXX: Deprecated, used only for dynamic Tooltips. Avoid using at all costs.

src/components/views/location/LocationButton.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export const LocationButton: React.FC<IProps> = ({ roomId, sender, menuPosition,
6969
iconClassName="mx_MessageComposer_location"
7070
onClick={openMenu}
7171
title={_t("Location")}
72+
inputRef={button}
7273
/>
7374

7475
{ contextMenu }

src/components/views/rooms/ReadReceiptGroup.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,8 @@ export function ReadReceiptGroup(
209209
onMouseOver={showTooltip}
210210
onMouseLeave={hideTooltip}
211211
onFocus={showTooltip}
212-
onBlur={hideTooltip}>
212+
onBlur={hideTooltip}
213+
>
213214
{ remText }
214215
<span
215216
className="mx_ReadReceiptGroup_container"

src/components/views/spaces/SpacePanel.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -204,9 +204,7 @@ const CreateSpaceButton = ({
204204
isPanelCollapsed,
205205
setPanelCollapsed,
206206
}: Pick<IInnerSpacePanelProps, "isPanelCollapsed" | "setPanelCollapsed">) => {
207-
// We don't need the handle as we position the menu in a constant location
208-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
209-
const [menuDisplayed, _handle, openMenu, closeMenu] = useContextMenu<void>();
207+
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu<HTMLElement>();
210208

211209
useEffect(() => {
212210
if (!isPanelCollapsed && menuDisplayed) {
@@ -231,13 +229,14 @@ const CreateSpaceButton = ({
231229
role="treeitem"
232230
>
233231
<SpaceButton
234-
data-test-id='create-space-button'
232+
data-testid='create-space-button'
235233
className={classNames("mx_SpaceButton_new", {
236234
mx_SpaceButton_newCancel: menuDisplayed,
237235
})}
238236
label={menuDisplayed ? _t("Cancel") : _t("Create a space")}
239237
onClick={onNewClick}
240238
isNarrow={isPanelCollapsed}
239+
ref={handle}
241240
/>
242241

243242
{ contextMenu }

src/components/views/spaces/SpaceTreeLevel.tsx

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,16 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import React, { MouseEvent, ComponentProps, ComponentType, createRef, InputHTMLAttributes, LegacyRef } from "react";
17+
import React, {
18+
MouseEvent,
19+
ComponentProps,
20+
ComponentType,
21+
createRef,
22+
InputHTMLAttributes,
23+
LegacyRef,
24+
forwardRef,
25+
RefObject,
26+
} from "react";
1827
import classNames from "classnames";
1928
import { Room, RoomEvent } from "matrix-js-sdk/src/models/room";
2029
import { DraggableProvidedDragHandleProps } from "react-beautiful-dnd";
@@ -54,7 +63,7 @@ interface IButtonProps extends Omit<ComponentProps<typeof AccessibleTooltipButto
5463
onClick?(ev?: ButtonEvent): void;
5564
}
5665

57-
export const SpaceButton: React.FC<IButtonProps> = ({
66+
export const SpaceButton = forwardRef<HTMLElement, IButtonProps>(({
5867
space,
5968
spaceKey,
6069
className,
@@ -67,9 +76,9 @@ export const SpaceButton: React.FC<IButtonProps> = ({
6776
children,
6877
ContextMenuComponent,
6978
...props
70-
}) => {
71-
const [menuDisplayed, ref, openMenu, closeMenu] = useContextMenu<HTMLElement>();
72-
const [onFocus, isActive, handle] = useRovingTabIndex(ref);
79+
}, ref: RefObject<HTMLElement>) => {
80+
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu<HTMLElement>(ref);
81+
const [onFocus, isActive] = useRovingTabIndex(handle);
7382
const tabIndex = isActive ? 0 : -1;
7483

7584
let avatar = <div className="mx_SpaceButton_avatarPlaceholder"><div className="mx_SpaceButton_icon" /></div>;
@@ -150,7 +159,7 @@ export const SpaceButton: React.FC<IButtonProps> = ({
150159
</div>
151160
</AccessibleTooltipButton>
152161
);
153-
};
162+
});
154163

155164
interface IItemProps extends InputHTMLAttributes<HTMLLIElement> {
156165
space: Room;

test/components/views/spaces/SpacePanel-test.tsx

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,13 @@ limitations under the License.
1515
*/
1616

1717
import React from 'react';
18-
// eslint-disable-next-line deprecate/import
19-
import { mount } from 'enzyme';
18+
import { render, screen, fireEvent } from "@testing-library/react";
2019
import { mocked } from 'jest-mock';
2120
import { MatrixClient } from 'matrix-js-sdk/src/matrix';
22-
import { act } from "react-dom/test-utils";
2321

2422
import SpacePanel from '../../../../src/components/views/spaces/SpacePanel';
2523
import { MatrixClientPeg } from '../../../../src/MatrixClientPeg';
2624
import { SpaceKey } from '../../../../src/stores/spaces';
27-
import { findByTestId } from '../../../test-utils';
2825
import { shouldShowComponent } from '../../../../src/customisations/helpers/UIComponents';
2926
import { UIComponent } from '../../../../src/settings/UIFeature';
3027

@@ -47,10 +44,6 @@ jest.mock('../../../../src/customisations/helpers/UIComponents', () => ({
4744
}));
4845

4946
describe('<SpacePanel />', () => {
50-
const defaultProps = {};
51-
const getComponent = (props = {}) =>
52-
mount(<SpacePanel {...defaultProps} {...props} />);
53-
5447
const mockClient = {
5548
getUserId: jest.fn().mockReturnValue('@test:test'),
5649
isGuest: jest.fn(),
@@ -67,26 +60,21 @@ describe('<SpacePanel />', () => {
6760

6861
describe('create new space button', () => {
6962
it('renders create space button when UIComponent.CreateSpaces component should be shown', () => {
70-
const component = getComponent();
71-
expect(findByTestId(component, 'create-space-button').length).toBeTruthy();
63+
render(<SpacePanel />);
64+
screen.getByTestId("create-space-button");
7265
});
7366

7467
it('does not render create space button when UIComponent.CreateSpaces component should not be shown', () => {
7568
mocked(shouldShowComponent).mockReturnValue(false);
76-
const component = getComponent();
69+
render(<SpacePanel />);
7770
expect(shouldShowComponent).toHaveBeenCalledWith(UIComponent.CreateSpaces);
78-
expect(findByTestId(component, 'create-space-button').length).toBeFalsy();
71+
expect(screen.queryByTestId("create-space-button")).toBeFalsy();
7972
});
8073

81-
it('opens context menu on create space button click', async () => {
82-
const component = getComponent();
83-
84-
await act(async () => {
85-
findByTestId(component, 'create-space-button').at(0).simulate('click');
86-
component.setProps({});
87-
});
88-
89-
expect(component.find('SpaceCreateMenu').length).toBeTruthy();
74+
it('opens context menu on create space button click', () => {
75+
render(<SpacePanel />);
76+
fireEvent.click(screen.getByTestId("create-space-button"));
77+
screen.getByTestId("create-space-button");
9078
});
9179
});
9280
});

0 commit comments

Comments
 (0)