Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions packages/core/src/extensions/SuggestionMenu/SuggestionMenu.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,4 +306,23 @@ describe("SuggestionMenu", () => {

editor._tiptapEditor.destroy();
});

it("does not throw when the menu opens while not editable (#2701)", () => {
const editor = createEditor();
editor.setTextCursorPosition(editor.document[0].id, "end");

// Toggling to non-editable right before the menu opens (as a controlled
// `editable` prop does under load) means the menu view never gets shown, so
// its `state` stays undefined. The stop/non-editable update path used to
// call `emitUpdate` unconditionally and throw.
editor.isEditable = false;

expect(() =>
editor
.getExtension(SuggestionMenu)!
.openSuggestionMenu("/", { deleteTriggerCharacter: true }),
).not.toThrow();

editor._tiptapEditor.destroy();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,15 @@ class SuggestionMenuView {
this.pluginState = stopped ? prev : next;

if (stopped || !this.editor.isEditable) {
// If the menu was never actually shown (`state` is still undefined - e.g.
// it was opened while the editor was not editable, which happens when the
// editable prop is toggled right before the menu opens), there is nothing
// to hide, and calling `emitUpdate` here would throw "Attempting to update
// uninitialized suggestions menu" (#2701).
if (this.state) {
this.state.show = false;
this.emitUpdate(this.pluginState!.triggerCharacter);
}
this.emitUpdate(this.pluginState!.triggerCharacter);

return;
}
Expand Down
Loading