Files
Tom Moor 79df2f2dc8 fix: Dropped content in Markdown parser with mixed checklist content (#11994)
* fix: Dropped content in Markdown parser with mixed checklist content

* fix: Treat non-checkbox items as unchecked in mixed checkbox lists

When a bullet list contains a mix of checkbox and regular items, the
markdown-it checkbox rule converts the list to a checkbox_list but
leaves non-checkbox items as list_item tokens. Since the Prosemirror
schema requires checkbox_item+ children, these invalid list_item nodes
cause the entire list to be silently dropped — explaining the content
truncation reported in #11988.

Convert remaining list_item tokens that are direct children of a
checkbox_list into unchecked checkbox_item tokens. Uses a level stack
to avoid converting nested bullet/ordered list items.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: Move checkbox tests to collocated checkboxes.test.ts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 08:07:28 -04:00

148 lines
3.5 KiB
TypeScript

import { parser, serializer } from ".";
test("renders an empty doc", () => {
const ast = parser.parse("");
expect(ast?.toJSON()).toEqual({
content: [{ type: "paragraph" }],
type: "doc",
});
});
test("parses lowercase alpha lists", () => {
const ast = parser.parse("a. First item\nb. Second item");
expect(ast?.toJSON()).toEqual({
content: [
{
attrs: { listStyle: "lower-alpha", order: 1 },
content: [
{
content: [
{
content: [{ text: "First item", type: "text" }],
type: "paragraph",
},
],
type: "list_item",
},
{
content: [
{
content: [{ text: "Second item", type: "text" }],
type: "paragraph",
},
],
type: "list_item",
},
],
type: "ordered_list",
},
],
type: "doc",
});
});
test("parses uppercase alpha lists", () => {
const ast = parser.parse("A. First item\nB. Second item");
expect(ast?.toJSON()).toEqual({
content: [
{
attrs: { listStyle: "upper-alpha", order: 1 },
content: [
{
content: [
{
content: [{ text: "First item", type: "text" }],
type: "paragraph",
},
],
type: "list_item",
},
{
content: [
{
content: [{ text: "Second item", type: "text" }],
type: "paragraph",
},
],
type: "list_item",
},
],
type: "ordered_list",
},
],
type: "doc",
});
});
test("parses alpha lists with blank lines (issue example)", () => {
const markdown = `## 3. Step Three
a. Do this.
b. Do that.`;
const ast = parser.parse(markdown);
const json = ast?.toJSON();
// Find the ordered_list in the result
const orderedList = json?.content?.find(
(node: { type: string }) => node.type === "ordered_list"
);
expect(orderedList).toBeDefined();
expect(orderedList?.attrs.listStyle).toBe("lower-alpha");
expect(orderedList?.attrs.order).toBe(1);
expect(orderedList?.content).toHaveLength(2);
});
test("preserves numeric lists", () => {
const ast = parser.parse("1. First item\n2. Second item");
expect(ast?.toJSON()).toEqual({
content: [
{
attrs: { listStyle: "number", order: 1 },
content: [
{
content: [
{
content: [{ text: "First item", type: "text" }],
type: "paragraph",
},
],
type: "list_item",
},
{
content: [
{
content: [{ text: "Second item", type: "text" }],
type: "paragraph",
},
],
type: "list_item",
},
],
type: "ordered_list",
},
],
type: "doc",
});
});
test("serializes lowercase alpha lists back to markdown", () => {
const ast = parser.parse("a. First item\nb. Second item");
const output = serializer.serialize(ast);
expect(output.trim()).toBe("a. First item\nb. Second item");
});
test("serializes uppercase alpha lists back to markdown", () => {
const ast = parser.parse("A. First item\nB. Second item");
const output = serializer.serialize(ast);
expect(output.trim()).toBe("A. First item\nB. Second item");
});