mirror of
https://github.com/block/buzz.git
synced 2026-10-30 22:11:36 +00:00
fix(desktop): guard unmounted formatting tray editor
Co-authored-by: Arjun Mahanti <arjun@squareup.com> Signed-off-by: Arjun Mahanti <arjun@squareup.com>
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { after, afterEach, before, test } from "node:test";
|
||||
|
||||
import { JSDOM } from "jsdom";
|
||||
|
||||
const dom = new JSDOM("<!doctype html><html><body></body></html>", {
|
||||
url: "http://localhost",
|
||||
});
|
||||
|
||||
before(() => {
|
||||
Object.assign(globalThis, {
|
||||
document: dom.window.document,
|
||||
Element: dom.window.Element,
|
||||
HTMLElement: dom.window.HTMLElement,
|
||||
IS_REACT_ACT_ENVIRONMENT: true,
|
||||
Node: dom.window.Node,
|
||||
window: dom.window,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
const { cleanup } = await import("@testing-library/react");
|
||||
cleanup();
|
||||
});
|
||||
|
||||
after(() => dom.window.close());
|
||||
|
||||
test("does not read the TipTap view while the editor is unmounted", async () => {
|
||||
const React = await import("react");
|
||||
const { render } = await import("@testing-library/react");
|
||||
const { SelectionFormattingTray } = await import(
|
||||
"./SelectionFormattingTray.tsx"
|
||||
);
|
||||
let viewReads = 0;
|
||||
const unmountedEditor = {
|
||||
get isDestroyed() {
|
||||
return true;
|
||||
},
|
||||
get view() {
|
||||
viewReads += 1;
|
||||
throw new Error("TipTap view is not mounted");
|
||||
},
|
||||
};
|
||||
|
||||
render(
|
||||
React.createElement(
|
||||
React.StrictMode,
|
||||
null,
|
||||
React.createElement(SelectionFormattingTray, {
|
||||
editor: unmountedEditor,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
assert.equal(viewReads, 0);
|
||||
});
|
||||
@@ -26,6 +26,8 @@ function clamp(value: number, min: number, max: number): number {
|
||||
}
|
||||
|
||||
function getSelectionRect(editor: Editor): DOMRect | null {
|
||||
if (editor.isDestroyed) return null;
|
||||
|
||||
const { from, to } = editor.state.selection;
|
||||
|
||||
try {
|
||||
@@ -46,15 +48,19 @@ function getSelectionRect(editor: Editor): DOMRect | null {
|
||||
// Fall back to the caret coordinates below.
|
||||
}
|
||||
|
||||
const startCoords = editor.view.coordsAtPos(from);
|
||||
const endCoords = editor.view.coordsAtPos(to);
|
||||
const left = Math.min(startCoords.left, endCoords.left);
|
||||
const right = Math.max(startCoords.right, endCoords.right);
|
||||
const top = Math.min(startCoords.top, endCoords.top);
|
||||
const bottom = Math.max(startCoords.bottom, endCoords.bottom);
|
||||
try {
|
||||
const startCoords = editor.view.coordsAtPos(from);
|
||||
const endCoords = editor.view.coordsAtPos(to);
|
||||
const left = Math.min(startCoords.left, endCoords.left);
|
||||
const right = Math.max(startCoords.right, endCoords.right);
|
||||
const top = Math.min(startCoords.top, endCoords.top);
|
||||
const bottom = Math.max(startCoords.bottom, endCoords.bottom);
|
||||
|
||||
if (right <= left && bottom <= top) return null;
|
||||
return new DOMRect(left, top, Math.max(1, right - left), bottom - top);
|
||||
if (right <= left && bottom <= top) return null;
|
||||
return new DOMRect(left, top, Math.max(1, right - left), bottom - top);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getTrayPosition(
|
||||
@@ -129,6 +135,7 @@ export function SelectionFormattingTray({
|
||||
if (
|
||||
suppressRightClickUpdatesRef.current ||
|
||||
!editor ||
|
||||
editor.isDestroyed ||
|
||||
disabled ||
|
||||
!editor.isEditable ||
|
||||
!editor.isFocused
|
||||
@@ -155,13 +162,20 @@ export function SelectionFormattingTray({
|
||||
React.useEffect(() => {
|
||||
suppressRightClickUpdatesRef.current = false;
|
||||
|
||||
if (!editor) {
|
||||
if (!editor || editor.isDestroyed) {
|
||||
cancelScheduledUpdate();
|
||||
setPosition(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const editorDom = editor.view.dom;
|
||||
let editorDom: HTMLElement;
|
||||
try {
|
||||
editorDom = editor.view.dom;
|
||||
} catch {
|
||||
cancelScheduledUpdate();
|
||||
setPosition(null);
|
||||
return;
|
||||
}
|
||||
const hide = () => setPosition(null);
|
||||
const handleContextMenu = () => {
|
||||
suppressRightClickUpdatesRef.current = true;
|
||||
|
||||
Reference in New Issue
Block a user