mirror of
https://github.com/outline/outline.git
synced 2026-06-13 03:14:59 +03:00
79df2f2dc8
* 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>
148 lines
3.5 KiB
TypeScript
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");
|
|
});
|