hello
", 38 | ), 39 | ( 40 | "it can represent a line break", 41 | doc(p("hi", br, "there")), 42 | "hi
there
hithere
onetwothreefourfive
", 53 | ), 54 | ( 55 | "it can represent links", 56 | doc( 57 | p( 58 | "a ", 59 | a({"href": "foo"}, "big ", a({"href": "bar"}, "nested"), " link"), 60 | ), 61 | ), 62 | '', 64 | ), 65 | ( 66 | "it can represent an unordered list", 67 | doc( 68 | ul(li(p("one")), li(p("two")), li(p("three", strong("!")))), 69 | p("after"), 70 | ), 71 | "one
two
three" 72 | "!
after
", 73 | ), 74 | ( 75 | "it can represent an ordered list", 76 | doc( 77 | ol(li(p("one")), li(p("two")), li(p("three", strong("!")))), 78 | p("after"), 79 | ), 80 | "one
two
three" 81 | "!
after
", 82 | ), 83 | ( 84 | "it can represent a blockquote", 85 | doc(blockquote(p("hello"), p("bye"))), 86 | "", 87 | ), 88 | ( 89 | "it can represent headings", 90 | doc(h1("one"), h2("two"), p("text")), 91 | "hello
bye
text
", 92 | ), 93 | ( 94 | "it can represent inline code", 95 | doc(p("text and ", code("code that is ", em("emphasized"), "..."))), 96 | "text and code that is
emphasized
"
97 | "...
some code
and
", 103 | ), 104 | ( 105 | "it supports leaf nodes in marks", 106 | doc(p(em("hi", br, "x"))), 107 | "hi
x
\u00a0 \u00a0hello\u00a0
", 113 | ), 114 | ], 115 | ) 116 | def test_serializer_first(doc, html, desc): 117 | """Parser is not implemented, this is just testing serializer right now""" 118 | schema = doc.type.schema 119 | dom = DOMSerializer.from_schema(schema).serialize_fragment(doc.content) 120 | assert str(dom) == html, desc 121 | 122 | 123 | @pytest.mark.parametrize( 124 | ("desc", "serializer", "doc", "expect"), 125 | [ 126 | ( 127 | "it can omit a mark", 128 | no_em, 129 | p("foo", em("bar"), strong("baz")), 130 | "foobarbaz", 131 | ), 132 | ( 133 | "it doesn't split other marks for omitted marks", 134 | no_em, 135 | p("foo", code("bar"), em(code("baz"), "quux"), "xyz"), 136 | "foobarbaz
quuxxyz",
137 | ),
138 | (
139 | "it can render marks with complex structure",
140 | DOMSerializer(
141 | serializer.nodes,
142 | {
143 | **serializer.marks,
144 | "em": lambda *_: ["em", ["i", {"data-emphasis": "true"}, 0]],
145 | },
146 | ),
147 | p(strong("foo", code("bar"), em(code("baz"))), em("quux"), "xyz"),
148 | "foobar
"
149 | 'baz
'
150 | "quuxxyz",
151 | ),
152 | ],
153 | )
154 | def test_serializer(serializer, doc, expect, desc):
155 | assert str(serializer.serialize_fragment(doc.content)) == expect, desc
156 |
157 |
158 | def test_html_is_escaped():
159 | assert (
160 | str(serializer.serialize_node(schema.text("bold &")))
161 | == "<b>bold &</b>"
162 | )
163 |
164 |
165 | @pytest.mark.parametrize(
166 | ("desc", "doc", "expect"),
167 | [
168 | (
169 | "Basic text node",
170 | """test
186 | test 187 |
188 |test some bolded text
test some bolded text
another test """ 223 | """em
test google\nsome more text here""" 255 | """
Hello
Test break
Another bit """
257 | """of testing data.
result of
this""", 350 | { 351 | "type": "doc", 352 | "content": [ 353 | { 354 | "type": "paragraph", 355 | "content": [{"type": "text", "text": "Testing the"}], 356 | }, 357 | { 358 | "type": "paragraph", 359 | "content": [ 360 | {"type": "text", "text": "result "}, 361 | { 362 | "type": "text", 363 | "marks": [{"type": "strong", "attrs": {}}], 364 | "text": "o", 365 | }, 366 | { 367 | "type": "text", 368 | "marks": [ 369 | {"type": "em", "attrs": {}}, 370 | {"type": "strong", "attrs": {}}, 371 | ], 372 | "text": "f", 373 | }, 374 | ], 375 | }, 376 | { 377 | "type": "paragraph", 378 | "content": [{"type": "text", "text": " this"}], 379 | }, 380 | ], 381 | }, 382 | ), 383 | ], 384 | ) 385 | def test_parser(doc, expect, desc): 386 | """ 387 | The `expect` dicts are straight copies from the output of the JS lib run in Node, 388 | with 1 exception of 'attrs' key in marks dicts, in JS if blank attrs isn't written, 389 | this library does write out 'attrs' even if it is blank, I didn't want to modify 390 | behavior of existing files with the addition of this 391 | """ 392 | assert from_html(schema, doc) == expect, desc 393 | -------------------------------------------------------------------------------- /tests/prosemirror_model/tests/test_mark.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from prosemirror.model import Mark, Node, Schema 4 | from prosemirror.test_builder import out 5 | from prosemirror.test_builder import test_schema as schema 6 | 7 | doc = out["doc"] 8 | p = out["p"] 9 | em = out["em"] 10 | a = out["a"] 11 | 12 | em_ = schema.mark("em") 13 | strong = schema.mark("strong") 14 | 15 | 16 | def link(href, title=None): 17 | return schema.mark("link", {"href": href, "title": title}) 18 | 19 | 20 | code = schema.mark("code") 21 | 22 | 23 | custom_schema = Schema({ 24 | "nodes": { 25 | "doc": {"content": "paragraph+"}, 26 | "paragraph": {"content": "text*"}, 27 | "text": {}, 28 | }, 29 | "marks": { 30 | "remark": {"attrs": {"id": {}}, "excludes": "", "inclusive": False}, 31 | "user": {"attrs": {"id": {}}, "excludes": "_"}, 32 | "strong": {"excludes": "em-group"}, 33 | "em": {"group": "em-group"}, 34 | }, 35 | }) 36 | 37 | custom = custom_schema.marks 38 | remark1 = custom["remark"].create({"id": 1}) 39 | remark2 = custom["remark"].create({"id": 2}) 40 | user1 = custom["user"].create({"id": 1}) 41 | user2 = custom["user"].create({"id": 2}) 42 | custom_em = custom["em"].create() 43 | custom_strong = custom["strong"].create() 44 | 45 | 46 | @pytest.mark.parametrize( 47 | ("a", "b", "res"), 48 | [ 49 | ([em_, strong], [em_, strong], True), 50 | ([em_, strong], [em_, code], False), 51 | ([em_, strong], [em_, strong, code], False), 52 | ([link("http://foo"), code], [link("http://foo"), code], True), 53 | ([link("http://foo"), code], [link("http://bar"), code], False), 54 | ], 55 | ) 56 | def test_same_set(a, b, res): 57 | assert Mark.same_set(a, b) is res 58 | 59 | 60 | @pytest.mark.parametrize( 61 | ("a", "b", "res"), 62 | [ 63 | (link("http://foo"), (link("http://foo")), True), 64 | (link("http://foo"), link("http://bar"), False), 65 | (link("http://foo", "A"), link("http://foo", "B"), False), 66 | ], 67 | ) 68 | def test_eq(a, b, res): 69 | assert a.eq(b) is res 70 | 71 | 72 | def test_add_to_set(ist): 73 | ist(em_.add_to_set([]), [em_], Mark.same_set) 74 | ist(em_.add_to_set([em_]), [em_], Mark.same_set) 75 | ist(em_.add_to_set([strong]), [em_, strong], Mark.same_set) 76 | ist(strong.add_to_set([em_]), [em_, strong], Mark.same_set) 77 | ist( 78 | link("http://bar").add_to_set([link("http://foo"), em_]), 79 | [link("http://bar"), em_], 80 | Mark.same_set, 81 | ) 82 | ist( 83 | link("http://foo").add_to_set([em_, link("http://foo")]), 84 | [em_, link("http://foo")], 85 | Mark.same_set, 86 | ) 87 | ist( 88 | code.add_to_set([em_, strong, link("http://foo")]), 89 | [em_, strong, link("http://foo"), code], 90 | Mark.same_set, 91 | ) 92 | ist(strong.add_to_set([em_, code]), [em_, strong, code], Mark.same_set) 93 | ist(remark2.add_to_set([remark1]), [remark1, remark2], Mark.same_set) 94 | ist(remark1.add_to_set([remark1]), [remark1], Mark.same_set) 95 | ist(user1.add_to_set([remark1, custom_em]), [user1], Mark.same_set) 96 | ist(custom_em.add_to_set([user1]), [user1], Mark.same_set) 97 | ist(user2.add_to_set([user1]), [user2], Mark.same_set) 98 | ist( 99 | custom_em.add_to_set([remark1, custom_strong]), 100 | [remark1, custom_strong], 101 | Mark.same_set, 102 | ) 103 | ist( 104 | custom_strong.add_to_set([remark1, custom_em]), 105 | [remark1, custom_strong], 106 | Mark.same_set, 107 | ) 108 | 109 | 110 | def test_remove_form_set(ist): 111 | ist(Mark.same_set(em_.remove_from_set([]), [])) 112 | ist(Mark.same_set(em_.remove_from_set([em_]), [])) 113 | ist(Mark.same_set(strong.remove_from_set([em_]), [em_])) 114 | ist(Mark.same_set(link("http://foo").remove_from_set([link("http://foo")]), [])) 115 | ist( 116 | Mark.same_set( 117 | link("http://foo", "title").remove_from_set([link("http://foo")]), 118 | [link("http://foo")], 119 | ), 120 | ) 121 | 122 | 123 | class TestResolvedPosMarks: 124 | custom_doc = Node.from_json( 125 | custom_schema, 126 | { 127 | "type": "doc", 128 | "content": [ 129 | { 130 | "type": "paragraph", 131 | "content": [ 132 | { 133 | "type": "text", 134 | "marks": [ 135 | {"type": "remark", "attrs": {"id": 1}}, 136 | {"type": "strong"}, 137 | ], 138 | "text": "one", 139 | }, 140 | {"type": "text", "text": "two"}, 141 | ], 142 | }, 143 | { 144 | "type": "paragraph", 145 | "content": [ 146 | {"type": "text", "text": "one"}, 147 | { 148 | "type": "text", 149 | "marks": [{"type": "remark", "attrs": {"id": 1}}], 150 | "text": "twothree", 151 | }, 152 | ], 153 | }, 154 | { 155 | "type": "paragraph", 156 | "content": [ 157 | { 158 | "type": "text", 159 | "marks": [{"type": "remark", "attrs": {"id": 2}}], 160 | "text": "one", 161 | }, 162 | { 163 | "type": "text", 164 | "marks": [{"type": "remark", "attrs": {"id": 1}}], 165 | "text": "two", 166 | }, 167 | ], 168 | }, 169 | ], 170 | }, 171 | ) 172 | 173 | @pytest.mark.parametrize( 174 | ("doc", "mark", "result"), 175 | [ 176 | (doc(p(em("foo"))), em_, True), 177 | (doc(p(em("foo"))), strong, False), 178 | (doc(p(em("hi"), " there")), em_, True), 179 | (doc(p("one ", em("two"))), em_, False), 180 | (doc(p(em("one"))), em_, True), 181 | (doc(p(a("link"))), link("http://baz"), False), 182 | ], 183 | ) 184 | def test_is_at(self, doc, mark, result): 185 | assert mark.is_in_set(doc.resolve(doc.tag["a"]).marks()) is result 186 | 187 | @pytest.mark.parametrize( 188 | ("a", "b"), 189 | [ 190 | (custom_doc.resolve(4).marks(), [custom_strong]), 191 | (custom_doc.resolve(3).marks(), [remark1, custom_strong]), 192 | (custom_doc.resolve(20).marks(), []), 193 | (custom_doc.resolve(15).marks(), [remark1]), 194 | (custom_doc.resolve(25).marks(), []), 195 | ], 196 | ) 197 | def test_with_custom_doc(self, a, b): 198 | assert Mark.same_set(a, b) 199 | -------------------------------------------------------------------------------- /tests/prosemirror_model/tests/test_node.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from prosemirror.model import Fragment, Schema 4 | from prosemirror.test_builder import eq, out 5 | from prosemirror.test_builder import test_schema as schema 6 | 7 | doc = out["doc"] 8 | blockquote = out["blockquote"] 9 | p = out["p"] 10 | li = out["li"] 11 | ul = out["ul"] 12 | em = out["em"] 13 | strong = out["strong"] 14 | code = out["code"] 15 | a = out["a"] 16 | br = out["br"] 17 | hr = out["hr"] 18 | img = out["img"] 19 | 20 | custom_schema: Schema[ 21 | Literal["doc", "paragraph", "text", "contact", "hard_break"], 22 | str, 23 | ] = Schema({ 24 | "nodes": { 25 | "doc": {"content": "paragraph+"}, 26 | "paragraph": {"content": "(text|contact)*"}, 27 | "text": { 28 | "toDebugString": lambda _: "custom_text", 29 | }, 30 | "contact": { 31 | "inline": True, 32 | "attrs": {"name": {}, "email": {}}, 33 | "leafText": (lambda node: f"{node.attrs['name']} <{node.attrs['email']}>"), 34 | }, 35 | "hard_break": { 36 | "toDebugString": lambda _: "custom_hard_break", 37 | }, 38 | }, 39 | }) 40 | 41 | 42 | class TestToString: 43 | def test_nesting(self): 44 | node = doc(ul(li(p("hey"), p()), li(p("foo")))) 45 | expected = 'doc(bullet_list(list_item(paragraph("hey"), paragraph), list_item(paragraph("foo"))))' # noqa 46 | assert str(node) == expected 47 | 48 | def test_shows_inline_children(self): 49 | node = doc(p("foo", img, br, "bar")) 50 | assert str(node) == 'doc(paragraph("foo", image, hard_break, "bar"))' 51 | 52 | def test_shows_marks(self): 53 | node = doc(p("foo", em("bar", strong("quux")), code("baz"))) 54 | expected = 'doc(paragraph("foo", em("bar"), em(strong("quux")), code("baz")))' 55 | assert str(node) == expected 56 | 57 | def test_has_default_tostring_method_text(self): 58 | assert str(schema.text("hello")) == '"hello"' 59 | 60 | def test_has_default_tostring_method_br(self): 61 | assert str(br()) == "hard_break" 62 | 63 | def test_nodespec_to_debug_string(self): 64 | assert str(custom_schema.text("hello")) == "custom_text" 65 | 66 | def test_respected_by_fragment(self): 67 | f = Fragment.from_array( 68 | [ 69 | custom_schema.text("hello"), 70 | custom_schema.nodes["hard_break"].create_checked(), 71 | custom_schema.text("world"), 72 | ], 73 | ) 74 | assert str(f) == "(2,2)' 90 | -------------------------------------------------------------------------------- /tests/prosemirror_transform/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fellowapp/prosemirror-py/c996d5e23a8d6ef7360db26bf91f815a86a1587a/tests/prosemirror_transform/__init__.py -------------------------------------------------------------------------------- /tests/prosemirror_transform/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fellowapp/prosemirror-py/c996d5e23a8d6ef7360db26bf91f815a86a1587a/tests/prosemirror_transform/tests/__init__.py -------------------------------------------------------------------------------- /tests/prosemirror_transform/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from prosemirror.model import Fragment, Slice 4 | from prosemirror.test_builder import out 5 | from prosemirror.test_builder import test_schema as schema 6 | from prosemirror.transform import ( 7 | AddMarkStep, 8 | Mapping, 9 | RemoveMarkStep, 10 | ReplaceStep, 11 | Step, 12 | StepMap, 13 | Transform, 14 | ) 15 | 16 | doc = out["doc"] 17 | p = out["p"] 18 | 19 | 20 | @pytest.fixture 21 | def test_mapping(): 22 | def t_mapping(mapping, *cases): 23 | inverted = mapping.invert() 24 | for case in cases: 25 | from_, to, bias, lossy = ( 26 | lambda from_, to, bias=1, lossy=False: (from_, to, bias, lossy) 27 | )(*case) 28 | assert mapping.map(from_, bias) == to 29 | if not lossy: 30 | assert inverted.map(to, bias) == from_ 31 | 32 | return t_mapping 33 | 34 | 35 | @pytest.fixture 36 | def make_mapping(): 37 | def mk(*args): 38 | mapping = Mapping() 39 | for arg in args: 40 | if isinstance(arg, list): 41 | mapping.append_map(StepMap(arg)) 42 | else: 43 | for from_ in arg: 44 | mapping.set_mirror(from_, arg[from_]) 45 | return mapping 46 | 47 | return mk 48 | 49 | 50 | @pytest.fixture 51 | def test_del(): 52 | def t_del(mapping: Mapping, pos: int, side: int, flags: str): 53 | r = mapping.map_result(pos, side) 54 | found = "" 55 | if r.deleted: 56 | found += "d" 57 | if r.deleted_before: 58 | found += "b" 59 | if r.deleted_after: 60 | found += "a" 61 | if r.deleted_across: 62 | found += "x" 63 | assert found == flags 64 | 65 | return t_del 66 | 67 | 68 | @pytest.fixture 69 | def make_step(): 70 | return _make_step 71 | 72 | 73 | def _make_step(from_: int, to: int, val: str | None) -> Step: 74 | if val == "+em": 75 | return AddMarkStep(from_, to, schema.marks["em"].create()) 76 | elif val == "-em": 77 | return RemoveMarkStep(from_, to, schema.marks["em"].create()) 78 | return ReplaceStep( 79 | from_, 80 | to, 81 | Slice.empty if val is None else Slice(Fragment.from_(schema.text(val)), 0, 0), 82 | ) 83 | 84 | 85 | @pytest.fixture 86 | def test_doc(): 87 | return doc(p("foobar")) 88 | 89 | 90 | _test_doc = doc(p("foobar")) 91 | 92 | 93 | @pytest.fixture 94 | def test_transform(): 95 | def invert(transform): 96 | out = Transform(transform.doc) 97 | for i, step in reversed(list(enumerate(transform.steps))): 98 | out.step(step.invert(transform.docs[i])) 99 | return out 100 | 101 | def test_step_json(tr): 102 | new_tr = Transform(tr.before) 103 | for step in tr.steps: 104 | new_tr.step(Step.from_json(tr.doc.type.schema, step.to_json())) 105 | 106 | def test_mapping(mapping, pos, new_pos): 107 | mapped = mapping.map(pos, 1) 108 | assert mapped == new_pos 109 | remap = Mapping([m.invert() for m in mapping.maps]) 110 | for i, map in enumerate(reversed(mapping.maps)): 111 | remap.append_map(map, len(mapping.maps) - 1 - i) 112 | assert remap.map(pos, 1) == pos 113 | 114 | def test_transform(tr, expect): 115 | assert tr.doc.eq(expect) 116 | assert invert(tr).doc.eq(tr.before) 117 | test_step_json(tr) 118 | 119 | for tag in expect.tag: 120 | test_mapping(tr.mapping, tr.before.tag.get(tag), expect.tag.get(tag)) 121 | 122 | return test_transform 123 | -------------------------------------------------------------------------------- /tests/prosemirror_transform/tests/test_mapping.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.mark.parametrize( 5 | ("mapping_info", "cases"), 6 | [ 7 | ([[2, 0, 4]], [[0, 0], [2, 6], [2, 2, -1], [3, 7]]), 8 | ( 9 | [[2, 4, 0]], 10 | [[0, 0], [2, 2, -1], [3, 2, 1, True], [6, 2, 1], [6, 2, -1, True], [7, 3]], 11 | ), 12 | ( 13 | [[2, 4, 4]], 14 | [[0, 0], [2, 2, 1], [4, 6, 1, True], [4, 2, -1, True], [6, 6, -1], [8, 8]], 15 | ), 16 | ([[2, 4, 0], [2, 0, 4], {0: 1}], [[0, 0], [2, 2], [4, 4], [6, 6], [7, 7]]), 17 | ([[2, 0, 4], [2, 4, 0], {0: 1}], [[0, 0], [2, 2], [3, 3]]), 18 | ( 19 | [[2, 4, 0], [1, 0, 1], [3, 0, 4], {0: 2}], 20 | [[0, 0], [1, 2], [4, 5], [6, 7], [7, 8]], 21 | ), 22 | ], 23 | ) 24 | def test_all_mapping_cases(mapping_info, cases, test_mapping, make_mapping): 25 | test_mapping(make_mapping(*mapping_info), *cases) 26 | 27 | 28 | @pytest.mark.parametrize( 29 | ("mapping_info", "pos", "side", "flags"), 30 | [ 31 | (([0, 2, 0],), 2, -1, "db"), 32 | (([0, 2, 0],), 2, 1, "b"), 33 | (([0, 2, 2],), 2, -1, "db"), 34 | ( 35 | ( 36 | [0, 1, 0], 37 | [0, 1, 0], 38 | ), 39 | 2, 40 | -1, 41 | "db", 42 | ), 43 | (([0, 1, 0],), 2, -1, ""), 44 | (([2, 2, 0],), 2, -1, "a"), 45 | (([2, 2, 0],), 2, 1, "da"), 46 | (([2, 2, 2],), 2, 1, "da"), 47 | ( 48 | ( 49 | [2, 1, 0], 50 | [2, 1, 0], 51 | ), 52 | 2, 53 | 1, 54 | "da", 55 | ), 56 | (([3, 2, 0],), 2, -1, ""), 57 | (([0, 4, 0],), 2, -1, "dbax"), 58 | (([0, 4, 0],), 2, 1, "dbax"), 59 | ( 60 | ( 61 | [0, 1, 0], 62 | [4, 1, 0], 63 | [0, 3, 0], 64 | ), 65 | 2, 66 | 1, 67 | "dbax", 68 | ), 69 | ( 70 | ( 71 | [4, 1, 0], 72 | [0, 1, 0], 73 | ), 74 | 2, 75 | -1, 76 | "", 77 | ), 78 | ( 79 | ( 80 | [2, 1, 0], 81 | [0, 2, 0], 82 | ), 83 | 2, 84 | -1, 85 | "dba", 86 | ), 87 | ( 88 | ( 89 | [2, 1, 0], 90 | [0, 1, 0], 91 | ), 92 | 2, 93 | -1, 94 | "a", 95 | ), 96 | ( 97 | ( 98 | [3, 1, 0], 99 | [0, 2, 0], 100 | ), 101 | 2, 102 | -1, 103 | "db", 104 | ), 105 | ], 106 | ) 107 | def test_all_del_cases(mapping_info, pos, side, flags, test_del, make_mapping): 108 | test_del(make_mapping(*mapping_info), pos, side, flags) 109 | -------------------------------------------------------------------------------- /tests/prosemirror_transform/tests/test_step.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from .conftest import _make_step, _test_doc 4 | 5 | 6 | def yes(from1, to1, val1, from2, to2, val2): 7 | def inner(): 8 | step1 = _make_step(from1, to1, val1) 9 | step2 = _make_step(from2, to2, val2) 10 | merged = step1.merge(step2) 11 | assert merged 12 | assert merged.apply(_test_doc).doc.eq( 13 | step2.apply(step1.apply(_test_doc).doc).doc, 14 | ) 15 | 16 | return inner 17 | 18 | 19 | def no(from1, to1, val1, from2, to2, val2): 20 | def inner(): 21 | step1 = _make_step(from1, to1, val1) 22 | step2 = _make_step(from2, to2, val2) 23 | merged = step1.merge(step2) 24 | assert merged is None 25 | 26 | return inner 27 | 28 | 29 | @pytest.mark.parametrize( 30 | ("pass_", "from1", "to1", "val1", "from2", "to2", "val2"), 31 | [ 32 | (yes, 2, 2, "a", 3, 3, "b"), 33 | (yes, 2, 2, "a", 2, 2, "b"), 34 | (no, 2, 2, "a", 4, 4, "b"), 35 | (no, 3, 3, "a", 2, 2, "b"), 36 | (yes, 3, 4, None, 2, 3, None), 37 | (yes, 2, 3, None, 2, 3, None), 38 | (no, 1, 2, None, 2, 3, None), 39 | (yes, 2, 3, None, 2, 2, "x"), 40 | (yes, 2, 2, "quux", 6, 6, "baz"), 41 | (yes, 2, 2, "quux", 2, 2, "baz"), 42 | (yes, 2, 5, None, 2, 4, None), 43 | (yes, 4, 6, None, 2, 4, None), 44 | (yes, 3, 4, "x", 4, 5, "y"), 45 | (yes, 1, 2, "+em", 2, 4, "+em"), 46 | (yes, 1, 3, "+em", 2, 4, "+em"), 47 | (no, 1, 2, "+em", 3, 4, "+em"), 48 | (yes, 1, 2, "-em", 2, 4, "-em"), 49 | (yes, 1, 3, "-em", 2, 4, "-em"), 50 | (no, 1, 2, "-em", 3, 4, "-em"), 51 | ], 52 | ) 53 | def test_all_cases(pass_, from1, to1, val1, from2, to2, val2): 54 | pass_(from1, to1, val1, from2, to2, val2)() 55 | -------------------------------------------------------------------------------- /tests/prosemirror_transform/tests/test_structure.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from prosemirror.model import Schema, Slice 4 | from prosemirror.transform import Transform, can_split, find_wrapping, lift_target 5 | from prosemirror.transform.structure import NodeTypeWithAttrs 6 | 7 | schema = Schema({ 8 | "nodes": { 9 | "doc": {"content": "head? block* sect* closing?"}, 10 | "para": {"content": "text*", "group": "block"}, 11 | "head": {"content": "text*", "marks": ""}, 12 | "figure": {"content": "caption figureimage", "group": "block"}, 13 | "quote": {"content": "block+", "group": "block"}, 14 | "figureimage": {}, 15 | "caption": {"content": "text*", "marks": ""}, 16 | "sect": {"content": "head block* sect*"}, 17 | "closing": {"content": "text*"}, 18 | "text": {"group": "inline"}, 19 | "fixed": {"content": "head para closing", "group": "block"}, 20 | }, 21 | "marks": {"em": {}}, 22 | }) 23 | 24 | 25 | def n(name, *content): 26 | return schema.nodes[name].create(None, list(content)) 27 | 28 | 29 | def t(str, em=None): 30 | return schema.text(str, [schema.mark["em"]] if em else None) 31 | 32 | 33 | doc = n( 34 | "doc", # 0 35 | n("head", t("Head")), # 6 36 | n("para", t("Intro")), # 13 37 | n( 38 | "sect", # 14 39 | n("head", t("Section head")), # 28 40 | n( 41 | "sect", # 29 42 | n("head", t("Subsection head")), # 46 43 | n("para", t("Subtext")), # 55 44 | n( 45 | "figure", 46 | n("caption", t("Figure caption")), 47 | n("figureimage"), 48 | ), # 56 # 72 # 74 49 | n("quote", n("para", t("!"))), 50 | ), 51 | ), # 81 52 | n("sect", n("head", t("S2")), n("para", t("Yes"))), # 82 # 86 # 92 53 | n("closing", t("fin")), 54 | ) # 97 55 | 56 | 57 | def range_(pos, end=None): 58 | return doc.resolve(pos).block_range(None if end is None else doc.resolve(end)) 59 | 60 | 61 | def fill(params, length): 62 | new_params = [] 63 | for item in params: 64 | item = list(item) 65 | diff = length - len(item) 66 | if diff > 0: 67 | item += [None] * diff 68 | new_params.append(item) 69 | return new_params 70 | 71 | 72 | class TestCanSplit: 73 | @pytest.mark.parametrize( 74 | ("pass_", "pos", "depth", "after"), 75 | fill( 76 | [ 77 | (False, 0), 78 | (False, 3), 79 | (True, 3, 1, "para"), 80 | (False, 6), 81 | (True, 8), 82 | (False, 14), 83 | (False, 17), 84 | (True, 17, 2), 85 | (True, 18, 1, "para"), 86 | (False, 46), 87 | (True, 48), 88 | (False, 60), 89 | (False, 62, 2), 90 | (False, 72), 91 | (True, 76), 92 | (True, 77, 2), 93 | (False, 97), 94 | ], 95 | 4, 96 | ), 97 | ) 98 | def test_can_split(self, pass_, pos, depth, after): 99 | res = can_split( 100 | doc, 101 | pos, 102 | depth, 103 | [NodeTypeWithAttrs(type=schema.nodes[after])] if after else None, 104 | ) 105 | if pass_: 106 | assert res 107 | else: 108 | assert not res 109 | 110 | def test_doesnt_return_true_when_split_content_doesnt_fit_in_given_node_type( 111 | self, 112 | ): 113 | s = Schema({ 114 | "nodes": { 115 | "doc": {"content": "chapter+"}, 116 | "para": {"content": "text*", "group": "block"}, 117 | "head": {"content": "text*", "marks": ""}, 118 | "figure": {"content": "caption figureimage", "group": "block"}, 119 | "quote": {"content": "block+", "group": "block"}, 120 | "figureimage": {}, 121 | "caption": {"content": "text*", "marks": ""}, 122 | "sect": {"content": "head block* sect*"}, 123 | "closing": {"content": "text*"}, 124 | "text": {"group": "inline"}, 125 | "fixed": {"content": "head para closing", "group": "block"}, 126 | "title": {"content": "text*"}, 127 | "chapter": {"content": "title scene+"}, 128 | "scene": {"content": "para+"}, 129 | }, 130 | }) 131 | assert not can_split( 132 | s.node( 133 | "doc", 134 | None, 135 | s.node( 136 | "chapter", 137 | None, 138 | [ 139 | s.node("title", None, s.text("title")), 140 | s.node("scene", None, s.node("para", None, s.text("scene"))), 141 | ], 142 | ), 143 | ), 144 | 4, 145 | 1, 146 | [NodeTypeWithAttrs(s.nodes["scene"])], 147 | ) 148 | 149 | 150 | class TestLiftTarget: 151 | @pytest.mark.parametrize( 152 | ("pass_", "pos"), 153 | [(False, 0), (False, 3), (False, 52), (False, 70), (True, 76), (False, 86)], 154 | ) 155 | def test_lift_target(self, pass_, pos): 156 | r = range_(pos) 157 | if pass_: 158 | assert bool(r and lift_target(r)) 159 | else: 160 | assert not bool(r and lift_target(r)) 161 | 162 | 163 | class TestFindWrapping: 164 | @pytest.mark.parametrize( 165 | ("pass_", "pos", "end", "type"), 166 | [ 167 | (True, 0, 92, "sect"), 168 | (False, 4, 4, "sect"), 169 | (True, 8, 8, "quote"), 170 | (False, 18, 18, "quote"), 171 | (True, 55, 74, "quote"), 172 | (False, 90, 90, "figure"), 173 | ], 174 | ) 175 | def test_find_wrapping(self, pass_, pos, end, type): 176 | r = range_(pos, end) 177 | if pass_: 178 | assert find_wrapping(r, schema.nodes[type]) 179 | else: 180 | assert not bool(find_wrapping(r, schema.nodes[type])) 181 | 182 | 183 | @pytest.mark.parametrize( 184 | ("doc", "from_", "to", "content", "open_start", "open_end", "result"), 185 | [ 186 | ( 187 | n("doc", n("sect", n("head", t("foo")), n("para", t("bar")))), 188 | 6, 189 | 6, 190 | n("doc", n("sect"), n("sect")), 191 | 1, 192 | 1, 193 | n( 194 | "doc", 195 | n("sect", n("head", t("foo"))), 196 | n("sect", n("head"), n("para", t("bar"))), 197 | ), 198 | ), 199 | ( 200 | n("doc", n("para", t("a")), n("para", t("b"))), 201 | 3, 202 | 3, 203 | n("doc", n("closing", t("."))), 204 | 0, 205 | 0, 206 | n("doc", n("para", t("a")), n("para", t("b"))), 207 | ), 208 | ( 209 | n("doc", n("sect", n("head", t("foo")), n("para", t("bar")))), 210 | 1, 211 | 3, 212 | n("doc", n("sect"), n("sect", n("head", t("hi")))), 213 | 1, 214 | 2, 215 | n( 216 | "doc", 217 | n("sect", n("head")), 218 | n("sect", n("head", t("hioo")), n("para", t("bar"))), 219 | ), 220 | ), 221 | ( 222 | n("doc"), 223 | 0, 224 | 0, 225 | n("doc", n("figure", n("figureimage"))), 226 | 1, 227 | 0, 228 | n("doc", n("figure", n("caption"), n("figureimage"))), 229 | ), 230 | ( 231 | n("doc"), 232 | 0, 233 | 0, 234 | n("doc", n("figure", n("caption"))), 235 | 0, 236 | 1, 237 | n("doc", n("figure", n("caption"), n("figureimage"))), 238 | ), 239 | ( 240 | n( 241 | "doc", 242 | n("figure", n("caption"), n("figureimage")), 243 | n("figure", n("caption"), n("figureimage")), 244 | ), 245 | 3, 246 | 8, 247 | None, 248 | 0, 249 | 0, 250 | n("doc", n("figure", n("caption"), n("figureimage"))), 251 | ), 252 | ( 253 | n("doc", n("sect", n("head"), n("figure", n("caption"), n("figureimage")))), 254 | 7, 255 | 9, 256 | n("doc", n("para", t("hi"))), 257 | 0, 258 | 0, 259 | n( 260 | "doc", 261 | n( 262 | "sect", 263 | n("head"), 264 | n("figure", n("caption"), n("figureimage")), 265 | n("para", t("hi")), 266 | ), 267 | ), 268 | ), 269 | ], 270 | ) 271 | def test_replace(doc, from_, to, content, open_start, open_end, result): 272 | slice = Slice(content.content, open_start, open_end) if content else Slice.empty 273 | tr = Transform(doc).replace(from_, to, slice) 274 | assert tr.doc.eq(result) 275 | --------------------------------------------------------------------------------