From 762d1b486ad4da0d3d4e92b2a7325c85d8ba08c9 Mon Sep 17 00:00:00 2001 From: wcyat Date: Tue, 9 Dec 2025 01:03:51 +0800 Subject: [PATCH] fix: ensure numbered list start property always present (#2241) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The parse method now always returns an object with the 'start' property, eliminating the RangeError when ProseMirror expects all schema-defined attributes to be present. The value is undefined when not needed, matching the previous implicit behavior. Added unit test that directly tests the parse() function to ensure it always returns an object with the 'start' property. This test catches the bug that the old code had (returning defaultProps without 'start'). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- .../blocks/ListItem/NumberedListItem/block.ts | 6 +- .../unit/core/blocks/NumberedListItem.test.ts | 101 +++++++++++++++++ .../issue2241NumberedListStartProperty.json | 104 ++++++++++++++++++ .../parse/parseTestInstances.ts | 13 +++ 4 files changed, 219 insertions(+), 5 deletions(-) create mode 100644 tests/src/unit/core/blocks/NumberedListItem.test.ts create mode 100644 tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/issue2241NumberedListStartProperty.json diff --git a/packages/core/src/blocks/ListItem/NumberedListItem/block.ts b/packages/core/src/blocks/ListItem/NumberedListItem/block.ts index 677d02523a..f3b5f108de 100644 --- a/packages/core/src/blocks/ListItem/NumberedListItem/block.ts +++ b/packages/core/src/blocks/ListItem/NumberedListItem/block.ts @@ -51,13 +51,9 @@ export const createNumberedListItemBlockSpec = createBlockSpec( const defaultProps = parseDefaultProps(element); - if (element.previousElementSibling || startIndex === 1) { - return defaultProps; - } - return { ...defaultProps, - start: startIndex, + start: element.previousElementSibling || startIndex === 1 ? undefined : startIndex, }; } diff --git a/tests/src/unit/core/blocks/NumberedListItem.test.ts b/tests/src/unit/core/blocks/NumberedListItem.test.ts new file mode 100644 index 0000000000..0187b2470d --- /dev/null +++ b/tests/src/unit/core/blocks/NumberedListItem.test.ts @@ -0,0 +1,101 @@ +import { describe, expect, it } from "vitest"; +import { createNumberedListItemBlockSpec } from "../../../../../packages/core/src/blocks/ListItem/NumberedListItem/block.js"; + +describe("NumberedListItem parse() method", () => { + const blockSpec = createNumberedListItemBlockSpec(); + const parseFunc = blockSpec.implementation.parse; + + if (!parseFunc) { + throw new Error("parse function not found"); + } + + it("should always return an object with 'start' property - first item, startIndex=1", () => { + // Create mock DOM elements + const li = document.createElement("li"); + const ol = document.createElement("ol"); + ol.setAttribute("start", "1"); + ol.appendChild(li); + + const result = parseFunc(li); + + // The parse function should return an object with 'start' property + expect(result).toBeDefined(); + expect(result).toHaveProperty("start"); + expect(result?.start).toBeUndefined(); // Should be undefined for first item at index 1 + }); + + it("should always return an object with 'start' property - first item, startIndex=5", () => { + const li = document.createElement("li"); + const ol = document.createElement("ol"); + ol.setAttribute("start", "5"); + ol.appendChild(li); + + const result = parseFunc(li); + + expect(result).toBeDefined(); + expect(result).toHaveProperty("start"); + expect(result?.start).toBe(5); // Should be 5 for first item at non-standard index + }); + + it("should always return an object with 'start' property - subsequent item", () => { + const li1 = document.createElement("li"); + const li2 = document.createElement("li"); + const ol = document.createElement("ol"); + ol.setAttribute("start", "1"); + ol.appendChild(li1); + ol.appendChild(li2); + + const result = parseFunc(li2); + + expect(result).toBeDefined(); + expect(result).toHaveProperty("start"); + expect(result?.start).toBeUndefined(); // Subsequent items don't need explicit start + }); + + it("should always return an object with 'start' property - subsequent item in list starting at 5", () => { + const li1 = document.createElement("li"); + const li2 = document.createElement("li"); + const ol = document.createElement("ol"); + ol.setAttribute("start", "5"); + ol.appendChild(li1); + ol.appendChild(li2); + + const result = parseFunc(li2); + + expect(result).toBeDefined(); + expect(result).toHaveProperty("start"); + expect(result?.start).toBeUndefined(); // Subsequent items get undefined + }); + + it("regression test for issue #2241 - ensures 'start' property is always present", () => { + // This test verifies the fix for issue #2241 + // The old code would return defaultProps (without 'start') for certain conditions + // The new code always includes 'start' in the returned object + + const testCases = [ + { startAttr: "1", hasPreSibling: false, expectedStart: undefined }, + { startAttr: "1", hasPreSibling: true, expectedStart: undefined }, + { startAttr: "5", hasPreSibling: false, expectedStart: 5 }, + { startAttr: "5", hasPreSibling: true, expectedStart: undefined }, + ]; + + testCases.forEach(({ startAttr, hasPreSibling, expectedStart }) => { + const li = document.createElement("li"); + const ol = document.createElement("ol"); + ol.setAttribute("start", startAttr); + + if (hasPreSibling) { + const li1 = document.createElement("li"); + ol.appendChild(li1); + } + + ol.appendChild(li); + + const result = parseFunc(li); + + // Critical assertion: 'start' property must ALWAYS be present + expect(result).toHaveProperty("start"); + expect(result?.start).toBe(expectedStart); + }); + }); +}); diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/issue2241NumberedListStartProperty.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/issue2241NumberedListStartProperty.json new file mode 100644 index 0000000000..b31cdba778 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/issue2241NumberedListStartProperty.json @@ -0,0 +1,104 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "First item", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Second item", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Third item", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "List starting at 5", + "type": "text", + }, + ], + "id": "4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Second item", + "type": "text", + }, + ], + "id": "5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Third item", + "type": "text", + }, + ], + "id": "6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/parseTestInstances.ts b/tests/src/unit/core/formatConversion/parse/parseTestInstances.ts index 86fe9157be..b3257825ae 100644 --- a/tests/src/unit/core/formatConversion/parse/parseTestInstances.ts +++ b/tests/src/unit/core/formatConversion/parse/parseTestInstances.ts @@ -1111,4 +1111,17 @@ Regular paragraph`, }, executeTest: testParseMarkdown, }, + { + testCase: { + name: "issue2241NumberedListStartProperty", + content: `1. First item +2. Second item +3. Third item + +5. List starting at 5 +6. Second item +7. Third item`, + }, + executeTest: testParseMarkdown, + }, ];