import "jest"; import { Schema } from "prosemirror-model"; import { markFactoryFactory, nodeFactoryFactory, RefsNode } from "../../composing"; import { mappableTextBetween } from "../mappableTextBetween"; const schema = new Schema({ nodes: { doc: { content: "group1+" }, hr: { group: "group1" }, ul: { group: "group1", content: "li+" }, li: { content: "p" }, p: { group: "group1", content: "(text | img)*" }, img: { inline: true }, text: {} }, marks: { bold: {} } }); const c = { bold: markFactoryFactory(schema.marks.bold), doc: nodeFactoryFactory(schema.nodes.doc), hr: nodeFactoryFactory(schema.nodes.hr), img: nodeFactoryFactory(schema.nodes.img), li: nodeFactoryFactory(schema.nodes.li), p: nodeFactoryFactory(schema.nodes.p), ul: nodeFactoryFactory(schema.nodes.ul) }; function test(name: string, { refMap, node }: RefsNode, expectedText: string, expectedDocLinesTextBetween: string[]) { it(name, () => { const startPos = refMap.get("<"); const endPos = refMap.get(">"); if (startPos === undefined) { throw new Error("Missing start reference, did you forget `{<}` ?"); } if (endPos === undefined) { throw new Error("Missing start reference, did you forget `{>}` ?"); } const { text, map } = mappableTextBetween(node, startPos, endPos); expect(text).toBe(expectedText); let cursor = 0; const splitText = "\n\n"; text.split(splitText).forEach((line, i) => { const [startPos, endPos] = map(cursor, cursor + line.length); expect(node.textBetween(startPos, endPos)).toBe(expectedDocLinesTextBetween[i]); cursor += line.length + splitText.length; }); }); } test("text block", c.doc(c.p("{<}hello world{>}")), "hello world", ["hello world"]); test("partial text block", c.doc(c.p("hel{<}lo wor{>}ld")), "lo wor", ["lo wor"]); test("partial text block over inline", c.doc(c.p("hel{<}lo", c.img(), " wor{>}ld")), "lo wor", ["lo wor"]); test("overlap horizontal rule", c.doc(c.p("he{<}llo"), c.hr(), c.p("wor{>}ld")), "llo\n\nwor", ["llo", "wor"]); test("across paragraph barrier", c.doc(c.p("he{<}llo"), c.p("wor{>}ld")), "llo\n\nwor", ["llo", "wor"]); test("across multiple paragraph barriers", c.doc(c.p("he{<}llo"), c.p("world"), c.p("en{>}d")), "llo\n\nworld\n\nen", [ "llo", "world", "en" ]); test("cross empty paragraphs", c.doc(c.p("he{<}llo"), c.p(), c.p("wor{>}ld")), "llo\n\nwor", ["llo", "wor"]); test("from empty paragraph", c.doc(c.p("{<}"), c.p("hello"), c.p(), c.p("wor{>}ld")), "\n\nhello\n\nwor", ["", "hello", "wor"]); test("from end of paragraph", c.doc(c.p("before{<}"), c.p("hello"), c.p(), c.p("wor{>}ld")), "\n\nhello\n\nwor", [ "", "hello", "wor" ]); test("start/end between nodes paragraph", c.doc("{<}", c.p("hello"), c.p(), c.p("world"), "{>}"), "hello\n\nworld", [ "hello", "world" ]); test( "whole doc with list items", c.doc("{<}", c.p("before"), c.ul(c.li(c.p("one")), c.li(c.p("two"))), c.p("after"), "{>}"), "before\n\none\n\ntwo\n\nafter", ["before", "one", "two", "after"] ); it("supports custom delimiters", () => { const { node, refMap } = c.doc(c.p("before{<}"), c.p("hello"), c.p(), c.p("wo", c.img(), "r{>}ld")); const { text } = mappableTextBetween(node, refMap.get("<")!, refMap.get(">")!, { block: "πŸ“¦", leaf: "🍁🍁" }); expect(text).toBe("πŸ“¦helloπŸ“¦wo🍁🍁r"); }); it("collapses multiple leaves and blocks into a single delimiter", () => { const { node, refMap } = c.doc(c.p("before{<}"), c.p("hello"), c.p(), c.p(), c.p("wo", c.img(), c.img(), "r{>}ld")); const { text } = mappableTextBetween(node, refMap.get("<")!, refMap.get(">")!); expect(text).toBe("\n\nhello\n\nwo r"); }); describe("inverse mapping", () => { it("from end of paragraph", () => { const { node, refMap } = c.doc(c.p("before{<}"), c.p("{a}hello{b}"), c.p(), c.p("{c}wor{d}{>}ld")); const { text, map } = mappableTextBetween(node, refMap.get("<")!, refMap.get(">")!); expect(text).toBe("\n\nhello\n\nwor"); // text diagram: // // \n \n h e l l o \n \n w o r // ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ // 0 1 2 3 4 5 6 7 8 9 10 // 11 expect(map(2, 7)).toMatchObject([refMap.get("a"), refMap.get("b")]); expect(map(9, 12)).toMatchObject([refMap.get("c"), refMap.get("d")]); }); it("between two-character block delimiters", () => { const { node, refMap } = c.doc(c.p("{<}"), c.p("{a}hello{b}"), c.p("{>}")); const { text, map } = mappableTextBetween(node, refMap.get("<")!, refMap.get(">")!); expect(text).toBe("\n\nhello\n\n"); // text diagram: // // \n \n h e l l o \n \n // ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ // 0 1 2 3 4 5 6 7 8 9 expect(map(0, 1)).toMatchObject([refMap.get("a"), refMap.get("a")]); expect(map(1, 2)).toMatchObject([refMap.get("a"), refMap.get("a")]); expect(map(0, 2)).toMatchObject([refMap.get("a"), refMap.get("a")]); expect(map(7, 8)).toMatchObject([refMap.get("b"), refMap.get("b")]); expect(map(8, 9)).toMatchObject([refMap.get("b"), refMap.get("b")]); expect(map(7, 9)).toMatchObject([refMap.get("b"), refMap.get("b")]); }); it("between three-character block delimiters", () => { const { node, refMap } = c.doc(c.p("{<}"), c.p("{a}hello{b}"), c.p("{>}")); const { text, map } = mappableTextBetween(node, refMap.get("<")!, refMap.get(">")!, { leaf: " ", block: "\n\n\n" }); expect(text).toBe("\n\n\nhello\n\n\n"); // text diagram: // // \n \n \n h e l l o \n \n \n // ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ // 0 1 2 3 4 5 6 7 8 9 10 11 expect(map(0, 0)).toMatchObject([refMap.get("a"), refMap.get("a")]); expect(map(0, 1)).toMatchObject([refMap.get("a"), refMap.get("a")]); expect(map(1, 2)).toMatchObject([refMap.get("a"), refMap.get("a")]); expect(map(0, 3)).toMatchObject([refMap.get("a"), refMap.get("a")]); expect(map(1, 3)).toMatchObject([refMap.get("a"), refMap.get("a")]); expect(map(2, 3)).toMatchObject([refMap.get("a"), refMap.get("a")]); expect(map(8, 9)).toMatchObject([refMap.get("b"), refMap.get("b")]); expect(map(9, 10)).toMatchObject([refMap.get("b"), refMap.get("b")]); expect(map(10, 11)).toMatchObject([refMap.get("b"), refMap.get("b")]); expect(map(8, 10)).toMatchObject([refMap.get("b"), refMap.get("b")]); expect(map(8, 11)).toMatchObject([refMap.get("b"), refMap.get("b")]); }); it("between three-character block delimiters (with multiple blocks)", () => { const { node, refMap } = c.doc(c.p("{<}"), c.p("{a}hello{b}"), c.p("{c}world{d}"), c.p("{>}")); const { text, map } = mappableTextBetween(node, refMap.get("<")!, refMap.get(">")!, { leaf: " ", block: "\n\n\n" }); expect(text).toBe("\n\n\nhello\n\n\nworld\n\n\n"); // text diagram: // // \n \n \n h e l l o \n \n \n w o r l d \n \n \n // ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ // 0 1 2 3 4 5 6 7 8 9 10 12 14 16 17 18 19 // 11 13 15 expect(map(0, 1)).toMatchObject([refMap.get("a"), refMap.get("a")]); expect(map(1, 2)).toMatchObject([refMap.get("a"), refMap.get("a")]); expect(map(0, 3)).toMatchObject([refMap.get("a"), refMap.get("a")]); expect(map(1, 3)).toMatchObject([refMap.get("a"), refMap.get("a")]); expect(map(2, 3)).toMatchObject([refMap.get("a"), refMap.get("a")]); expect(map(8, 8)).toMatchObject([refMap.get("c"), refMap.get("c")]); expect(map(8, 9)).toMatchObject([refMap.get("c"), refMap.get("c")]); expect(map(8, 10)).toMatchObject([refMap.get("c"), refMap.get("c")]); expect(map(8, 11)).toMatchObject([refMap.get("c"), refMap.get("c")]); }); it("between one-character block delimiters multiple blocks", () => { const { node, refMap } = c.doc(c.p("{<}"), c.p("{a}hello{b}"), c.p("{c}world{d}"), c.p("{>}")); const { text, map } = mappableTextBetween(node, refMap.get("<")!, refMap.get(">")!, { leaf: " ", block: "\n" }); expect(text).toBe("\nhello\nworld\n"); // text diagram: // // \n h e l l o \n w o r l d \n // ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ // 0 1 2 3 4 5 6 7 8 9 10 12 // 11 expect(map(0, 0)).toMatchObject([refMap.get("a"), refMap.get("a")]); expect(map(0, 1)).toMatchObject([refMap.get("a"), refMap.get("a")]); expect(map(1, 6)).toMatchObject([refMap.get("a"), refMap.get("b")]); expect(map(6, 6)).toMatchObject([refMap.get("c"), refMap.get("c")]); expect(map(6, 7)).toMatchObject([refMap.get("c"), refMap.get("c")]); expect(map(7, 7)).toMatchObject([refMap.get("c"), refMap.get("c")]); }); });