Skip to content

Commit 9513478

Browse files
committed
fixes bug with useEditorEffect being called with an already-unmounted EditorView
1 parent 4393863 commit 9513478

2 files changed

Lines changed: 105 additions & 3 deletions

File tree

src/hooks/__tests__/useEditorViewLayoutEffect.test.tsx

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
/* eslint-disable @typescript-eslint/no-empty-function */
22

33
import { render } from "@testing-library/react";
4-
import type { EditorState } from "prosemirror-state";
5-
import type { EditorView } from "prosemirror-view";
4+
import { Schema } from "prosemirror-model";
5+
import { EditorState } from "prosemirror-state";
6+
import { EditorView } from "prosemirror-view";
67
import React from "react";
78

89
import { LayoutGroup } from "../../components/LayoutGroup.js";
910
import { EditorContext } from "../../contexts/EditorContext.js";
11+
import {
12+
setupProseMirrorView,
13+
teardownProseMirrorView,
14+
} from "../../testing/setupProseMirrorView.js";
1015
import { useEditorEffect } from "../useEditorEffect.js";
1116

1217
function TestComponent({
@@ -126,4 +131,101 @@ describe("useEditorViewLayoutEffect", () => {
126131

127132
expect(effect).toHaveBeenCalledTimes(2);
128133
});
134+
135+
describe("with a real EditorView", () => {
136+
const schema = new Schema({
137+
nodes: {
138+
text: {},
139+
doc: { content: "text*" },
140+
},
141+
});
142+
143+
beforeAll(() => {
144+
setupProseMirrorView();
145+
});
146+
147+
afterAll(() => {
148+
teardownProseMirrorView();
149+
});
150+
151+
it("should not run the effect if the EditorView has been destroyed", () => {
152+
const editorState = EditorState.create({ schema });
153+
const mount = document.createElement("div");
154+
document.body.appendChild(mount);
155+
const editorView = new EditorView({ mount }, { state: editorState });
156+
157+
editorView.destroy();
158+
expect(editorView.isDestroyed).toBe(true);
159+
160+
const effect = jest.fn();
161+
162+
render(
163+
<LayoutGroup>
164+
<EditorContext.Provider
165+
value={{
166+
editorView,
167+
editorState,
168+
registerEventListener: () => {},
169+
unregisterEventListener: () => {},
170+
}}
171+
>
172+
<TestComponent effect={effect} />
173+
</EditorContext.Provider>
174+
</LayoutGroup>
175+
);
176+
177+
expect(effect).not.toHaveBeenCalled();
178+
179+
document.body.removeChild(mount);
180+
});
181+
182+
it("should not re-run the effect after the EditorView is destroyed between renders", () => {
183+
const editorState = EditorState.create({ schema });
184+
const mount = document.createElement("div");
185+
document.body.appendChild(mount);
186+
const editorView = new EditorView({ mount }, { state: editorState });
187+
188+
const effect = jest.fn();
189+
190+
const { rerender } = render(
191+
<LayoutGroup>
192+
<EditorContext.Provider
193+
value={{
194+
editorView,
195+
editorState,
196+
registerEventListener: () => {},
197+
unregisterEventListener: () => {},
198+
}}
199+
>
200+
<TestComponent effect={effect} dependencies={["one"]} />
201+
</EditorContext.Provider>
202+
</LayoutGroup>
203+
);
204+
205+
expect(effect).toHaveBeenCalledTimes(1);
206+
expect(effect).toHaveBeenCalledWith(editorView);
207+
208+
editorView.destroy();
209+
expect(editorView.isDestroyed).toBe(true);
210+
211+
rerender(
212+
<LayoutGroup>
213+
<EditorContext.Provider
214+
value={{
215+
editorView,
216+
editorState,
217+
registerEventListener: () => {},
218+
unregisterEventListener: () => {},
219+
}}
220+
>
221+
<TestComponent effect={effect} dependencies={["two"]} />
222+
</EditorContext.Provider>
223+
</LayoutGroup>
224+
);
225+
226+
expect(effect).toHaveBeenCalledTimes(1);
227+
228+
document.body.removeChild(mount);
229+
});
230+
});
129231
});

src/hooks/useEditorEffect.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export function useEditorEffect(
3434
// be defined inline and run on every re-render.
3535
useLayoutGroupEffect(
3636
() => {
37-
if (editorView) {
37+
if (editorView && !editorView.isDestroyed) {
3838
return effect(editorView);
3939
}
4040
},

0 commit comments

Comments
 (0)