From d7007a8e48ef37f7cbf3a83a80109a316b853107 Mon Sep 17 00:00:00 2001 From: Olov Date: Wed, 1 Apr 2026 12:04:28 -0400 Subject: [PATCH 1/4] bounds check --- .../createReactNodeViewConstructor.test.ts | 52 +++++++++++++++++++ .../createReactNodeViewConstructor.tsx | 2 + 2 files changed, 54 insertions(+) create mode 100644 src/nodeViews/__tests__/createReactNodeViewConstructor.test.ts diff --git a/src/nodeViews/__tests__/createReactNodeViewConstructor.test.ts b/src/nodeViews/__tests__/createReactNodeViewConstructor.test.ts new file mode 100644 index 0000000..49264fa --- /dev/null +++ b/src/nodeViews/__tests__/createReactNodeViewConstructor.test.ts @@ -0,0 +1,52 @@ +import { Schema } from "prosemirror-model"; +import { EditorState } from "prosemirror-state"; +import type { EditorView } from "prosemirror-view"; + +import { ROOT_NODE_KEY, react } from "../../plugins/react.js"; +import { findNodeKeyUp } from "../createReactNodeViewConstructor.js"; + +const schema = new Schema({ + nodes: { + doc: { content: "block+" }, + paragraph: { group: "block", content: "inline*" }, + text: { group: "inline" }, + }, +}); + +function createState(): EditorState { + return EditorState.create({ + doc: schema.nodes.doc.create(null, [schema.nodes.paragraph.create()]), + plugins: [react()], + }); +} + +/** + * findNodeKeyUp only reads editorView.state and editorView.props.nodeViews, + * so a minimal stub suffices and avoids needing toDOM specs for the schema. + */ +function stubView(state: EditorState): EditorView { + return { state, props: { nodeViews: {} } } as unknown as EditorView; +} + +describe("findNodeKeyUp", () => { + it("should return ROOT_NODE_KEY for a negative position", () => { + const view = stubView(createState()); + + expect(findNodeKeyUp(view, -1)).toBe(ROOT_NODE_KEY); + }); + + it("should return ROOT_NODE_KEY for a position beyond document size", () => { + const state = createState(); + const view = stubView(state); + + expect(findNodeKeyUp(view, state.doc.content.size + 1)).toBe( + ROOT_NODE_KEY + ); + }); + + it("should return ROOT_NODE_KEY for a valid position with no ancestor node views", () => { + const view = stubView(createState()); + + expect(findNodeKeyUp(view, 0)).toBe(ROOT_NODE_KEY); + }); +}); diff --git a/src/nodeViews/createReactNodeViewConstructor.tsx b/src/nodeViews/createReactNodeViewConstructor.tsx index b9300e7..087c2be 100644 --- a/src/nodeViews/createReactNodeViewConstructor.tsx +++ b/src/nodeViews/createReactNodeViewConstructor.tsx @@ -94,6 +94,8 @@ export function findNodeKeyUp(editorView: EditorView, pos: number): NodeKey { const pluginState = reactPluginKey.getState(editorView.state); if (!pluginState) return ROOT_NODE_KEY; + if (pos < 0 || pos > editorView.state.doc.content.size) return ROOT_NODE_KEY; + const $pos = editorView.state.doc.resolve(pos); for (let d = $pos.depth; d > 0; d--) { From 7ded03dc43b3f8f41781d760f90bd7a42319920b Mon Sep 17 00:00:00 2001 From: Olov Date: Wed, 1 Apr 2026 12:07:28 -0400 Subject: [PATCH 2/4] remove upper bound check --- .../__tests__/createReactNodeViewConstructor.test.ts | 9 --------- src/nodeViews/createReactNodeViewConstructor.tsx | 2 +- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/nodeViews/__tests__/createReactNodeViewConstructor.test.ts b/src/nodeViews/__tests__/createReactNodeViewConstructor.test.ts index 49264fa..8490eec 100644 --- a/src/nodeViews/__tests__/createReactNodeViewConstructor.test.ts +++ b/src/nodeViews/__tests__/createReactNodeViewConstructor.test.ts @@ -35,15 +35,6 @@ describe("findNodeKeyUp", () => { expect(findNodeKeyUp(view, -1)).toBe(ROOT_NODE_KEY); }); - it("should return ROOT_NODE_KEY for a position beyond document size", () => { - const state = createState(); - const view = stubView(state); - - expect(findNodeKeyUp(view, state.doc.content.size + 1)).toBe( - ROOT_NODE_KEY - ); - }); - it("should return ROOT_NODE_KEY for a valid position with no ancestor node views", () => { const view = stubView(createState()); diff --git a/src/nodeViews/createReactNodeViewConstructor.tsx b/src/nodeViews/createReactNodeViewConstructor.tsx index 087c2be..e381069 100644 --- a/src/nodeViews/createReactNodeViewConstructor.tsx +++ b/src/nodeViews/createReactNodeViewConstructor.tsx @@ -94,7 +94,7 @@ export function findNodeKeyUp(editorView: EditorView, pos: number): NodeKey { const pluginState = reactPluginKey.getState(editorView.state); if (!pluginState) return ROOT_NODE_KEY; - if (pos < 0 || pos > editorView.state.doc.content.size) return ROOT_NODE_KEY; + if (pos < 0) return ROOT_NODE_KEY; const $pos = editorView.state.doc.resolve(pos); From 971c25a04fa0b408435d0c525fce8a603a29fa7e Mon Sep 17 00:00:00 2001 From: Olov Date: Wed, 1 Apr 2026 12:13:55 -0400 Subject: [PATCH 3/4] patch version --- .yarn/versions/d7f962a1.yml | 0 package.json | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 .yarn/versions/d7f962a1.yml diff --git a/.yarn/versions/d7f962a1.yml b/.yarn/versions/d7f962a1.yml new file mode 100644 index 0000000..e69de29 diff --git a/package.json b/package.json index a599f5a..3d903e4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@nytimes/react-prosemirror", - "version": "2.0.0", + "version": "2.0.1", "license": "Apache-2.0", "type": "module", "main": "dist/cjs/index.js", From 1c286426a742d53d6e9b8c54004b161b5c227f40 Mon Sep 17 00:00:00 2001 From: Olov Date: Wed, 1 Apr 2026 13:08:10 -0400 Subject: [PATCH 4/4] Update createReactNodeViewConstructor.tsx --- src/nodeViews/createReactNodeViewConstructor.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/nodeViews/createReactNodeViewConstructor.tsx b/src/nodeViews/createReactNodeViewConstructor.tsx index e381069..f3127f8 100644 --- a/src/nodeViews/createReactNodeViewConstructor.tsx +++ b/src/nodeViews/createReactNodeViewConstructor.tsx @@ -94,7 +94,9 @@ export function findNodeKeyUp(editorView: EditorView, pos: number): NodeKey { const pluginState = reactPluginKey.getState(editorView.state); if (!pluginState) return ROOT_NODE_KEY; - if (pos < 0) return ROOT_NODE_KEY; + // -1 is the standard sentinel for nodes that are no longer part of the document + // since the node has no valid position, there are no ancestors to find + if (pos === -1) return ROOT_NODE_KEY; const $pos = editorView.state.doc.resolve(pos);