diff --git a/src/actions/actionAddToLibrary.ts b/src/actions/actionAddToLibrary.ts index a0abbf5c..8fb7eac9 100644 --- a/src/actions/actionAddToLibrary.ts +++ b/src/actions/actionAddToLibrary.ts @@ -17,6 +17,5 @@ export const actionAddToLibrary = register({ }); return false; }, - contextMenuOrder: 6, contextItemLabel: "labels.addToLibrary", }); diff --git a/src/actions/actionClipboard.tsx b/src/actions/actionClipboard.tsx new file mode 100644 index 00000000..727fab28 --- /dev/null +++ b/src/actions/actionClipboard.tsx @@ -0,0 +1,108 @@ +import { CODES, KEYS } from "../keys"; +import { register } from "./register"; +import { copyToClipboard } from "../clipboard"; +import { actionDeleteSelected } from "./actionDeleteSelected"; +import { getSelectedElements } from "../scene/selection"; +import { exportCanvas } from "../data/index"; +import { getNonDeletedElements } from "../element"; + +export const actionCopy = register({ + name: "copy", + perform: (elements, appState) => { + copyToClipboard(getNonDeletedElements(elements), appState); + + return { + commitToHistory: false, + }; + }, + contextItemLabel: "labels.copy", + keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.code === CODES.C, +}); + +export const actionCut = register({ + name: "cut", + perform: (elements, appState, data, app) => { + actionCopy.perform(elements, appState, data, app); + return actionDeleteSelected.perform(elements, appState, data, app); + }, + contextItemLabel: "labels.cut", + keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.code === CODES.X, +}); + +export const actionCopyAsSvg = register({ + name: "copyAsSvg", + perform: async (elements, appState, _data, app) => { + if (!app.canvas) { + return { + commitToHistory: false, + }; + } + const selectedElements = getSelectedElements( + getNonDeletedElements(elements), + appState, + ); + try { + await exportCanvas( + "clipboard-svg", + selectedElements.length + ? selectedElements + : getNonDeletedElements(elements), + appState, + app.canvas, + appState, + ); + return { + commitToHistory: false, + }; + } catch (error) { + console.error(error); + return { + appState: { + ...appState, + errorMessage: error.message, + }, + commitToHistory: false, + }; + } + }, + contextItemLabel: "labels.copyAsSvg", +}); + +export const actionCopyAsPng = register({ + name: "copyAsPng", + perform: async (elements, appState, _data, app) => { + if (!app.canvas) { + return { + commitToHistory: false, + }; + } + const selectedElements = getSelectedElements( + getNonDeletedElements(elements), + appState, + ); + try { + await exportCanvas( + "clipboard", + selectedElements.length + ? selectedElements + : getNonDeletedElements(elements), + appState, + app.canvas, + appState, + ); + return { + commitToHistory: false, + }; + } catch (error) { + console.error(error); + return { + appState: { + ...appState, + errorMessage: error.message, + }, + commitToHistory: false, + }; + } + }, + contextItemLabel: "labels.copyAsPng", +}); diff --git a/src/actions/actionDeleteSelected.tsx b/src/actions/actionDeleteSelected.tsx index 88e01450..dd2ad42c 100644 --- a/src/actions/actionDeleteSelected.tsx +++ b/src/actions/actionDeleteSelected.tsx @@ -136,7 +136,6 @@ export const actionDeleteSelected = register({ }; }, contextItemLabel: "labels.delete", - contextMenuOrder: 999999, keyTest: (event) => event.key === KEYS.BACKSPACE || event.key === KEYS.DELETE, PanelComponent: ({ elements, appState, updateData }) => ( enableActionGroup(elements, appState), @@ -174,7 +173,6 @@ export const actionUngroup = register({ }, keyTest: (event) => event.shiftKey && event[KEYS.CTRL_OR_CMD] && event.code === CODES.G, - contextMenuOrder: 5, contextItemLabel: "labels.ungroup", contextItemPredicate: (elements, appState) => getSelectedGroupIds(appState).length > 0, diff --git a/src/actions/actionStyles.ts b/src/actions/actionStyles.ts index dab50b63..4a8b54af 100644 --- a/src/actions/actionStyles.ts +++ b/src/actions/actionStyles.ts @@ -34,7 +34,6 @@ export const actionCopyStyles = register({ contextItemLabel: "labels.copyStyles", keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.C, - contextMenuOrder: 0, }); export const actionPasteStyles = register({ @@ -74,5 +73,4 @@ export const actionPasteStyles = register({ contextItemLabel: "labels.pasteStyles", keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.V, - contextMenuOrder: 1, }); diff --git a/src/actions/actionToggleGridMode.tsx b/src/actions/actionToggleGridMode.tsx new file mode 100644 index 00000000..22e8e558 --- /dev/null +++ b/src/actions/actionToggleGridMode.tsx @@ -0,0 +1,21 @@ +import { CODES, KEYS } from "../keys"; +import { register } from "./register"; +import { GRID_SIZE } from "../constants"; + +export const actionToggleGridMode = register({ + name: "gridMode", + perform(elements, appState) { + this.checked = !this.checked; + return { + appState: { + ...appState, + gridSize: this.checked ? GRID_SIZE : null, + }, + commitToHistory: false, + }; + }, + checked: false, + contextItemLabel: "labels.gridMode", + // Wrong event code + keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.code === CODES.QUOTE, +}); diff --git a/src/actions/actionToggleStats.tsx b/src/actions/actionToggleStats.tsx new file mode 100644 index 00000000..1d75caf8 --- /dev/null +++ b/src/actions/actionToggleStats.tsx @@ -0,0 +1,17 @@ +import { register } from "./register"; + +export const actionToggleStats = register({ + name: "stats", + perform(elements, appState) { + this.checked = !this.checked; + return { + appState: { + ...appState, + showStats: !appState.showStats, + }, + commitToHistory: false, + }; + }, + checked: false, + contextItemLabel: "stats.title", +}); diff --git a/src/actions/actionToggleZenMode.tsx b/src/actions/actionToggleZenMode.tsx new file mode 100644 index 00000000..32ddd6fe --- /dev/null +++ b/src/actions/actionToggleZenMode.tsx @@ -0,0 +1,20 @@ +import { CODES, KEYS } from "../keys"; +import { register } from "./register"; + +export const actionToggleZenMode = register({ + name: "zenMode", + perform(elements, appState) { + this.checked = !this.checked; + return { + appState: { + ...appState, + zenModeEnabled: this.checked, + }, + commitToHistory: false, + }; + }, + checked: false, + contextItemLabel: "buttons.zenMode", + // Wrong event code + keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.code === CODES.QUOTE, +}); diff --git a/src/actions/index.ts b/src/actions/index.ts index c5c44448..b335e44e 100644 --- a/src/actions/index.ts +++ b/src/actions/index.ts @@ -65,3 +65,15 @@ export { distributeHorizontally, distributeVertically, } from "./actionDistribute"; + +export { + actionCopy, + actionCut, + actionCopyAsPng, + actionCopyAsSvg, +} from "./actionClipboard"; + +export { actionToggleGridMode } from "./actionToggleGridMode"; +export { actionToggleZenMode } from "./actionToggleZenMode"; + +export { actionToggleStats } from "./actionToggleStats"; diff --git a/src/actions/manager.tsx b/src/actions/manager.tsx index 236a1441..d48e3b0e 100644 --- a/src/actions/manager.tsx +++ b/src/actions/manager.tsx @@ -3,14 +3,15 @@ import { Action, ActionsManagerInterface, UpdaterFn, - ActionFilterFn, ActionName, ActionResult, } from "./types"; import { ExcalidrawElement } from "../element/types"; import { AppState } from "../types"; -import { t } from "../i18n"; -import { ShortcutName } from "./shortcuts"; + +// This is the component, but for now we don't care about anything but its +// `canvas` state. +type App = { canvas: HTMLCanvasElement | null }; export class ActionManager implements ActionsManagerInterface { actions = {} as ActionsManagerInterface["actions"]; @@ -18,13 +19,14 @@ export class ActionManager implements ActionsManagerInterface { updater: (actionResult: ActionResult | Promise) => void; getAppState: () => Readonly; - getElementsIncludingDeleted: () => readonly ExcalidrawElement[]; + app: App; constructor( updater: UpdaterFn, getAppState: () => AppState, getElementsIncludingDeleted: () => readonly ExcalidrawElement[], + app: App, ) { this.updater = (actionResult) => { if (actionResult && "then" in actionResult) { @@ -37,6 +39,7 @@ export class ActionManager implements ActionsManagerInterface { }; this.getAppState = getAppState; this.getElementsIncludingDeleted = getElementsIncludingDeleted; + this.app = app; } registerAction(action: Action) { @@ -70,6 +73,7 @@ export class ActionManager implements ActionsManagerInterface { this.getElementsIncludingDeleted(), this.getAppState(), null, + this.app, ), ); return true; @@ -81,43 +85,11 @@ export class ActionManager implements ActionsManagerInterface { this.getElementsIncludingDeleted(), this.getAppState(), null, + this.app, ), ); } - getContextMenuItems(actionFilter: ActionFilterFn = (action) => action) { - return Object.values(this.actions) - .filter(actionFilter) - .filter((action) => "contextItemLabel" in action) - .filter((action) => - action.contextItemPredicate - ? action.contextItemPredicate( - this.getElementsIncludingDeleted(), - this.getAppState(), - ) - : true, - ) - .sort( - (a, b) => - (a.contextMenuOrder !== undefined ? a.contextMenuOrder : 999) - - (b.contextMenuOrder !== undefined ? b.contextMenuOrder : 999), - ) - .map((action) => ({ - // take last bit of the label "labels." - shortcutName: action.contextItemLabel?.split(".").pop() as ShortcutName, - label: action.contextItemLabel ? t(action.contextItemLabel) : "", - action: () => { - this.updater( - action.perform( - this.getElementsIncludingDeleted(), - this.getAppState(), - null, - ), - ); - }, - })); - } - // Id is an attribute that we can use to pass in data like keys. // This is needed for dynamically generated action components // like the user list. We can use this key to extract more @@ -132,6 +104,7 @@ export class ActionManager implements ActionsManagerInterface { this.getElementsIncludingDeleted(), this.getAppState(), formState, + this.app, ), ); }; diff --git a/src/actions/shortcuts.ts b/src/actions/shortcuts.ts index a12b30ac..a05c598d 100644 --- a/src/actions/shortcuts.ts +++ b/src/actions/shortcuts.ts @@ -9,7 +9,7 @@ export type ShortcutName = | "copyStyles" | "pasteStyles" | "selectAll" - | "delete" + | "deleteSelectedElements" | "duplicateSelection" | "sendBackward" | "bringForward" @@ -31,7 +31,7 @@ const shortcutMap: Record = { copyStyles: [getShortcutKey("CtrlOrCmd+Alt+C")], pasteStyles: [getShortcutKey("CtrlOrCmd+Alt+V")], selectAll: [getShortcutKey("CtrlOrCmd+A")], - delete: [getShortcutKey("Del")], + deleteSelectedElements: [getShortcutKey("Del")], duplicateSelection: [ getShortcutKey("CtrlOrCmd+D"), getShortcutKey(`Alt+${t("helpDialog.drag")}`), diff --git a/src/actions/types.ts b/src/actions/types.ts index ca30f567..6d373faa 100644 --- a/src/actions/types.ts +++ b/src/actions/types.ts @@ -16,12 +16,18 @@ type ActionFn = ( elements: readonly ExcalidrawElement[], appState: Readonly, formData: any, + app: { canvas: HTMLCanvasElement | null }, ) => ActionResult | Promise; export type UpdaterFn = (res: ActionResult) => void; export type ActionFilterFn = (action: Action) => void; export type ActionName = + | "copy" + | "cut" + | "paste" + | "copyAsPng" + | "copyAsSvg" | "sendBackward" | "bringForward" | "sendToBack" @@ -29,6 +35,9 @@ export type ActionName = | "copyStyles" | "selectAll" | "pasteStyles" + | "gridMode" + | "zenMode" + | "stats" | "changeStrokeColor" | "changeBackgroundColor" | "changeFillStyle" @@ -93,19 +102,16 @@ export interface Action { elements: readonly ExcalidrawElement[], ) => boolean; contextItemLabel?: string; - contextMenuOrder?: number; contextItemPredicate?: ( elements: readonly ExcalidrawElement[], appState: AppState, ) => boolean; + checked?: boolean; } export interface ActionsManagerInterface { actions: Record; registerAction: (action: Action) => void; handleKeyDown: (event: KeyboardEvent) => boolean; - getContextMenuItems: ( - actionFilter: ActionFilterFn, - ) => { label: string; action: () => void }[]; renderAction: (name: ActionName) => React.ReactElement | null; } diff --git a/src/components/App.tsx b/src/components/App.tsx index bf1c989b..0d400927 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -3,7 +3,28 @@ import React from "react"; import { RoughCanvas } from "roughjs/bin/canvas"; import rough from "roughjs/bin/rough"; import "../actions"; -import { actionDeleteSelected, actionFinalize } from "../actions"; +import { + actionAddToLibrary, + actionBringForward, + actionBringToFront, + actionCopy, + actionCopyAsPng, + actionCopyAsSvg, + actionCopyStyles, + actionCut, + actionDeleteSelected, + actionDuplicateSelection, + actionFinalize, + actionGroup, + actionPasteStyles, + actionSelectAll, + actionSendBackward, + actionSendToBack, + actionToggleGridMode, + actionToggleStats, + actionToggleZenMode, + actionUngroup, +} from "../actions"; import { createRedoAction, createUndoAction } from "../actions/actionHistory"; import { ActionManager } from "../actions/manager"; import { actions } from "../actions/register"; @@ -18,7 +39,6 @@ import { } from "../clipboard"; import { APP_NAME, - CANVAS_ONLY_ACTIONS, CURSOR_TYPE, DEFAULT_VERTICAL_ALIGN, DRAGGING_THRESHOLD, @@ -26,7 +46,6 @@ import { ELEMENT_TRANSLATE_AMOUNT, ENV, EVENT, - GRID_SIZE, LINE_CONFIRM_THRESHOLD, MIME_TYPES, POINTER_BUTTON, @@ -314,6 +333,7 @@ class App extends React.Component { this.syncActionResult, () => this.state, () => this.scene.getElementsIncludingDeleted(), + this, ); this.actionManager.registerAll(actions); @@ -927,25 +947,6 @@ class App extends React.Component { } }; - private copyToClipboardAsSvg = async () => { - const selectedElements = getSelectedElements( - this.scene.getElements(), - this.state, - ); - try { - await exportCanvas( - "clipboard-svg", - selectedElements.length ? selectedElements : this.scene.getElements(), - this.state, - this.canvas!, - this.state, - ); - } catch (error) { - console.error(error); - this.setState({ errorMessage: error.message }); - } - }; - private static resetTapTwice() { didTapTwice = false; } @@ -1148,15 +1149,11 @@ class App extends React.Component { }; toggleZenMode = () => { - this.setState({ - zenModeEnabled: !this.state.zenModeEnabled, - }); + this.actionManager.executeAction(actionToggleZenMode); }; toggleGridMode = () => { - this.setState({ - gridSize: this.state.gridSize ? null : GRID_SIZE, - }); + this.actionManager.executeAction(actionToggleGridMode); }; toggleStats = () => { @@ -3618,52 +3615,52 @@ class App extends React.Component { this.state, ); + const maybeGroupAction = actionGroup.contextItemPredicate!( + this.actionManager.getElementsIncludingDeleted(), + this.actionManager.getAppState(), + ); + + const maybeUngroupAction = actionUngroup.contextItemPredicate!( + this.actionManager.getElementsIncludingDeleted(), + this.actionManager.getAppState(), + ); + + const separator = "separator"; + const elements = this.scene.getElements(); const element = this.getElementAtPosition(x, y); if (!element) { ContextMenu.push({ options: [ navigator.clipboard && { - shortcutName: "paste", - label: t("labels.paste"), - action: () => this.pasteFromClipboard(null), + name: "paste", + perform: (elements, appStates) => { + this.pasteFromClipboard(null); + return { + commitToHistory: false, + }; + }, + contextItemLabel: "labels.paste", }, + separator, probablySupportsClipboardBlob && - elements.length > 0 && { - shortcutName: "copyAsPng", - label: t("labels.copyAsPng"), - action: this.copyToClipboardAsPng, - }, + elements.length > 0 && + actionCopyAsPng, probablySupportsClipboardWriteText && - elements.length > 0 && { - shortcutName: "copyAsSvg", - label: t("labels.copyAsSvg"), - action: this.copyToClipboardAsSvg, - }, - ...this.actionManager.getContextMenuItems((action) => - CANVAS_ONLY_ACTIONS.includes(action.name), - ), - { - checked: this.state.gridSize !== null, - shortcutName: "gridMode", - label: t("labels.gridMode"), - action: this.toggleGridMode, - }, - { - checked: this.state.zenModeEnabled, - shortcutName: "zenMode", - label: t("buttons.zenMode"), - action: this.toggleZenMode, - }, - { - checked: this.state.showStats, - shortcutName: "stats", - label: t("stats.title"), - action: this.toggleStats, - }, + elements.length > 0 && + actionCopyAsSvg, + ((probablySupportsClipboardBlob && elements.length > 0) || + (probablySupportsClipboardWriteText && elements.length > 0)) && + separator, + actionSelectAll, + separator, + actionToggleGridMode, + actionToggleZenMode, + actionToggleStats, ], top: clientY, left: clientX, + actionManager: this.actionManager, }); return; } @@ -3674,37 +3671,41 @@ class App extends React.Component { ContextMenu.push({ options: [ - { - shortcutName: "cut", - label: t("labels.cut"), - action: this.cutAll, - }, + actionCut, + navigator.clipboard && actionCopy, navigator.clipboard && { - shortcutName: "copy", - label: t("labels.copy"), - action: this.copyAll, + name: "paste", + perform: (elements, appStates) => { + this.pasteFromClipboard(null); + return { + commitToHistory: false, + }; + }, + contextItemLabel: "labels.paste", }, - navigator.clipboard && { - shortcutName: "paste", - label: t("labels.paste"), - action: () => this.pasteFromClipboard(null), - }, - probablySupportsClipboardBlob && { - shortcutName: "copyAsPng", - label: t("labels.copyAsPng"), - action: this.copyToClipboardAsPng, - }, - probablySupportsClipboardWriteText && { - shortcutName: "copyAsSvg", - label: t("labels.copyAsSvg"), - action: this.copyToClipboardAsSvg, - }, - ...this.actionManager.getContextMenuItems( - (action) => !CANVAS_ONLY_ACTIONS.includes(action.name), - ), + separator, + probablySupportsClipboardBlob && actionCopyAsPng, + probablySupportsClipboardWriteText && actionCopyAsSvg, + separator, + actionCopyStyles, + actionPasteStyles, + separator, + maybeGroupAction && actionGroup, + maybeUngroupAction && actionUngroup, + (maybeGroupAction || maybeUngroupAction) && separator, + actionAddToLibrary, + separator, + actionSendBackward, + actionBringForward, + actionSendToBack, + actionBringToFront, + separator, + actionDuplicateSelection, + actionDeleteSelected, ], top: clientY, left: clientX, + actionManager: this.actionManager, }); }; diff --git a/src/components/ContextMenu.scss b/src/components/ContextMenu.scss index 913a7ecc..8661cb93 100644 --- a/src/components/ContextMenu.scss +++ b/src/components/ContextMenu.scss @@ -9,9 +9,10 @@ list-style: none; user-select: none; margin: -0.25rem 0 0 0.125rem; - padding: 0.25rem 0; + padding: 0.5rem 0; background-color: var(--popup-secondary-background-color); border: 1px solid var(--button-gray-3); + cursor: default; } .context-menu button { @@ -88,4 +89,9 @@ } } } + + .context-menu-option-separator { + border: none; + border-top: 1px solid $oc-gray-5; + } } diff --git a/src/components/ContextMenu.tsx b/src/components/ContextMenu.tsx index e8ec9507..567d9dd4 100644 --- a/src/components/ContextMenu.tsx +++ b/src/components/ContextMenu.tsx @@ -2,31 +2,37 @@ import React from "react"; import { render, unmountComponentAtNode } from "react-dom"; import clsx from "clsx"; import { Popover } from "./Popover"; +import { t } from "../i18n"; import "./ContextMenu.scss"; import { getShortcutFromShortcutName, ShortcutName, } from "../actions/shortcuts"; +import { Action } from "../actions/types"; +import { ActionManager } from "../actions/manager"; -type ContextMenuOption = { - checked?: boolean; - shortcutName: ShortcutName; - label: string; - action(): void; -}; +type ContextMenuOption = "separator" | Action; -type Props = { +type ContextMenuProps = { options: ContextMenuOption[]; onCloseRequest?(): void; top: number; left: number; + actionManager: ActionManager; }; -const ContextMenu = ({ options, onCloseRequest, top, left }: Props) => { +const ContextMenu = ({ + options, + onCloseRequest, + top, + left, + actionManager, +}: ContextMenuProps) => { const isDarkTheme = !!document .querySelector(".excalidraw") ?.classList.contains("Appearance_dark"); + return (
{ className="context-menu" onContextMenu={(event) => event.preventDefault()} > - {options.map(({ action, checked, shortcutName, label }, idx) => ( -
  • - -
  • - ))} + {options.map((option, idx) => { + if (option === "separator") { + return
    ; + } + + const actionName = option.name; + const label = option.contextItemLabel + ? t(option.contextItemLabel) + : ""; + return ( +
  • + +
  • + ); + })}
    @@ -78,8 +94,9 @@ const getContextMenuNode = (): HTMLDivElement => { type ContextMenuParams = { options: (ContextMenuOption | false | null | undefined)[]; - top: number; - left: number; + top: ContextMenuProps["top"]; + left: ContextMenuProps["left"]; + actionManager: ContextMenuProps["actionManager"]; }; const handleClose = () => { @@ -101,6 +118,7 @@ export default { left={params.left} options={options} onCloseRequest={handleClose} + actionManager={params.actionManager} />, getContextMenuNode(), ); diff --git a/src/is-mobile.tsx b/src/is-mobile.tsx index c20bf89d..e12419fd 100644 --- a/src/is-mobile.tsx +++ b/src/is-mobile.tsx @@ -12,7 +12,7 @@ export const IsMobileProvider = ({ query.current = window.matchMedia ? window.matchMedia( // keep up to date with _variables.scss - "(max-width: 640px), (max-height: 500px) and (max-width: 1000px)", + "(max-width: 600px), (max-height: 500px) and (max-width: 1000px)", ) : (({ matches: false, diff --git a/src/keys.ts b/src/keys.ts index 54ec2e0e..4acc6ab9 100644 --- a/src/keys.ts +++ b/src/keys.ts @@ -19,6 +19,7 @@ export const CODES = { F: "KeyF", H: "KeyH", V: "KeyV", + X: "KeyX", Z: "KeyZ", } as const; diff --git a/src/tests/regressionTests.test.tsx b/src/tests/regressionTests.test.tsx index 95fb5662..2c73cef0 100644 --- a/src/tests/regressionTests.test.tsx +++ b/src/tests/regressionTests.test.tsx @@ -618,6 +618,7 @@ describe("regression tests", () => { clientY: 1, }); const contextMenu = document.querySelector(".context-menu"); + const contextMenuOptions = document.querySelectorAll(".context-menu li"); const expectedShortcutNames: ShortcutName[] = [ "selectAll", "gridMode", @@ -626,7 +627,7 @@ describe("regression tests", () => { ]; expect(contextMenu).not.toBeNull(); - expect(contextMenu?.children.length).toBe(expectedShortcutNames.length); + expect(contextMenuOptions.length).toBe(expectedShortcutNames.length); expectedShortcutNames.forEach((shortcutName) => { expect( contextMenu?.querySelector(`li[data-testid="${shortcutName}"]`), @@ -645,11 +646,12 @@ describe("regression tests", () => { clientY: 1, }); const contextMenu = document.querySelector(".context-menu"); + const contextMenuOptions = document.querySelectorAll(".context-menu li"); const expectedShortcutNames: ShortcutName[] = [ "cut", "copyStyles", "pasteStyles", - "delete", + "deleteSelectedElements", "addToLibrary", "sendBackward", "bringForward", @@ -659,7 +661,7 @@ describe("regression tests", () => { ]; expect(contextMenu).not.toBeNull(); - expect(contextMenu?.children.length).toBe(expectedShortcutNames.length); + expect(contextMenuOptions.length).toBe(expectedShortcutNames.length); expectedShortcutNames.forEach((shortcutName) => { expect( contextMenu?.querySelector(`li[data-testid="${shortcutName}"]`), @@ -689,11 +691,12 @@ describe("regression tests", () => { }); const contextMenu = document.querySelector(".context-menu"); + const contextMenuOptions = document.querySelectorAll(".context-menu li"); const expectedShortcutNames: ShortcutName[] = [ "cut", "copyStyles", "pasteStyles", - "delete", + "deleteSelectedElements", "group", "addToLibrary", "sendBackward", @@ -704,7 +707,7 @@ describe("regression tests", () => { ]; expect(contextMenu).not.toBeNull(); - expect(contextMenu?.children.length).toBe(expectedShortcutNames.length); + expect(contextMenuOptions.length).toBe(expectedShortcutNames.length); expectedShortcutNames.forEach((shortcutName) => { expect( contextMenu?.querySelector(`li[data-testid="${shortcutName}"]`), @@ -738,11 +741,12 @@ describe("regression tests", () => { }); const contextMenu = document.querySelector(".context-menu"); + const contextMenuOptions = document.querySelectorAll(".context-menu li"); const expectedShortcutNames: ShortcutName[] = [ "cut", "copyStyles", "pasteStyles", - "delete", + "deleteSelectedElements", "ungroup", "addToLibrary", "sendBackward", @@ -753,7 +757,7 @@ describe("regression tests", () => { ]; expect(contextMenu).not.toBeNull(); - expect(contextMenu?.children.length).toBe(expectedShortcutNames.length); + expect(contextMenuOptions.length).toBe(expectedShortcutNames.length); expectedShortcutNames.forEach((shortcutName) => { expect( contextMenu?.querySelector(`li[data-testid="${shortcutName}"]`),