fix(web): pass full ProseMirror doc to editor, not bare array
WritingView passed chapter.doc.content (a bare ProseMirror node array)
as the editor content, which made EditorState.create build a doc node
with an undefined type, throwing 'Cannot read properties of undefined
(reading schema)' and unmounting the write view. Pass the full doc
object ({ type: 'doc', content }) on init and on setContent sync.
Add editor regression tests covering schema instantiation, node
registration, and doc-content round-trip.
This commit is contained in:
@@ -57,7 +57,10 @@ export function WritingView({ onChapterChange }: WritingViewProps) {
|
|||||||
const projectId = project?.id ?? null;
|
const projectId = project?.id ?? null;
|
||||||
|
|
||||||
const { editor } = usePubScriptEditor({
|
const { editor } = usePubScriptEditor({
|
||||||
...(chapter ? { content: chapter.doc.content } : {}),
|
// Pass the full ProseMirror doc (`{ type: "doc", content }`), not the bare
|
||||||
|
// `content` array — TipTap/ProseMirror cannot build a doc node from a bare
|
||||||
|
// array, which would leave `doc.type` undefined and crash `EditorState.create`.
|
||||||
|
...(chapter ? { content: chapter.doc } : {}),
|
||||||
onUpdate: (ed) => {
|
onUpdate: (ed) => {
|
||||||
if (!chapter || !projectId) return;
|
if (!chapter || !projectId) return;
|
||||||
const json = ed.getJSON().content;
|
const json = ed.getJSON().content;
|
||||||
@@ -76,7 +79,7 @@ export function WritingView({ onChapterChange }: WritingViewProps) {
|
|||||||
// from re-entering onUpdate (which would otherwise re-save what we just loaded).
|
// from re-entering onUpdate (which would otherwise re-save what we just loaded).
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!editor || !chapter) return;
|
if (!editor || !chapter) return;
|
||||||
editor.commands.setContent(chapter.doc.content, { emitUpdate: false });
|
editor.commands.setContent(chapter.doc, { emitUpdate: false });
|
||||||
editor.commands.focus();
|
editor.commands.focus();
|
||||||
}, [editor, chapter?.id]);
|
}, [editor, chapter?.id]);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { pubScriptExtensions, PubScriptSchema } from "@pubscript/editor";
|
||||||
|
describe("schema", () => {
|
||||||
|
it("builds a non-empty schema", () => {
|
||||||
|
console.log("PubScriptSchema keys:", PubScriptSchema ? Object.keys(PubScriptSchema).join(",") : "UNDEFINED");
|
||||||
|
console.log("extensions count:", pubScriptExtensions.length);
|
||||||
|
expect(PubScriptSchema).toBeTruthy();
|
||||||
|
expect(PubScriptSchema.nodes.sceneBreak).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// @vitest-environment jsdom
|
||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { getSchema } from "@tiptap/core";
|
||||||
|
import { pubScriptExtensions, PubScriptSchema, createPubScriptEditor } from "@pubscript/editor";
|
||||||
|
|
||||||
|
describe("editor schema + instantiation", () => {
|
||||||
|
it("getSchema returns valid schema", () => {
|
||||||
|
console.log("PubScriptSchema nodes:", PubScriptSchema ? Object.keys(PubScriptSchema.nodes).join(",") : "UNDEFINED");
|
||||||
|
console.log("PubScriptSchema marks:", PubScriptSchema ? Object.keys(PubScriptSchema.marks).join(",") : "UNDEFINED");
|
||||||
|
expect(PubScriptSchema).toBeTruthy();
|
||||||
|
expect(PubScriptSchema.nodes.sceneBreak).toBeTruthy();
|
||||||
|
expect(PubScriptSchema.nodes.footnote).toBeTruthy();
|
||||||
|
expect(PubScriptSchema.nodes.footnoteArea).toBeTruthy();
|
||||||
|
expect(PubScriptSchema.nodes.characterMention).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("getSchema(pubScriptExtensions) matches PubScriptSchema", () => {
|
||||||
|
const s = getSchema(pubScriptExtensions);
|
||||||
|
console.log("inline getSchema nodes:", Object.keys(s.nodes).join(","));
|
||||||
|
expect(s).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("createPubScriptEditor does not throw", () => {
|
||||||
|
let editor;
|
||||||
|
try {
|
||||||
|
editor = createPubScriptEditor();
|
||||||
|
console.log("editor created, schema defined:", !!editor?.schema);
|
||||||
|
console.log("editor doc type:", editor?.state?.doc?.type?.name);
|
||||||
|
} catch (e) {
|
||||||
|
console.log("createPubScriptEditor THREW:", (e as Error).message);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
expect(editor).toBeTruthy();
|
||||||
|
expect(editor.schema).toBeTruthy();
|
||||||
|
expect(editor.state.doc.type).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { createPubScriptEditor } from "./schema.js";
|
||||||
|
|
||||||
|
/** Minimal full ProseMirror doc, mirroring a seeded chapter.doc. */
|
||||||
|
const fullDoc = {
|
||||||
|
type: "doc",
|
||||||
|
content: [
|
||||||
|
{ type: "heading", attrs: { level: 1 }, content: [{ type: "text", text: "The Glass Harbor" }] },
|
||||||
|
{ type: "paragraph", content: [{ type: "text", text: "Salt hung in the air." }] },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Bare array — the exact bug that crashed EditorState.create in the browser. */
|
||||||
|
const bareArray = fullDoc.content;
|
||||||
|
|
||||||
|
describe("editor content parsing (regression: bare array vs full doc)", () => {
|
||||||
|
it("builds an editor from a full doc object (the seeded chapter.doc shape)", () => {
|
||||||
|
const editor = createPubScriptEditor({ content: fullDoc });
|
||||||
|
expect(editor).not.toBeNull();
|
||||||
|
expect(editor.state.doc.type.name).toBe("doc");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws on a bare content array (documents the regression we fixed)", () => {
|
||||||
|
expect(() => createPubScriptEditor({ content: bareArray })).toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user