|
| 1 | +"""Style precedence through the public Markdown import and export paths.""" |
| 2 | + |
| 3 | +import pytest |
| 4 | + |
| 5 | +from substack.mdexport import document_to_markdown |
| 6 | +from substack.mdrender import markdown_to_doc |
| 7 | +from substack.post import Post |
| 8 | + |
| 9 | + |
| 10 | +def render(markdown): |
| 11 | + post = Post("T", "", user_id=1) |
| 12 | + post.from_markdown(markdown) |
| 13 | + return post.draft_body |
| 14 | + |
| 15 | + |
| 16 | +def marks_for(document, text): |
| 17 | + node = next(n for n in document["content"][0]["content"] if n["text"] == text) |
| 18 | + return node.get("marks", []) |
| 19 | + |
| 20 | + |
| 21 | +@pytest.mark.parametrize( |
| 22 | + ("property_name", "mark_type"), |
| 23 | + [("color", "textStyle"), ("background-color", "highlight")], |
| 24 | +) |
| 25 | +@pytest.mark.parametrize( |
| 26 | + "declarations", |
| 27 | + [ |
| 28 | + "{prop}:red; {prop}:blue", |
| 29 | + "{prop}:red; {prop}:green; {prop}:blue", |
| 30 | + "{prop}:red; {prop}:; font-weight:bold; {prop}:blue; {prop}:", |
| 31 | + "{prop}:red; {prop_upper} : blue", |
| 32 | + ], |
| 33 | +) |
| 34 | +def test_last_nonempty_style_declaration_wins(property_name, mark_type, declarations): |
| 35 | + style = declarations.format(prop=property_name, prop_upper=property_name.upper()) |
| 36 | + document = render(f'[x]{{style="{style}"}}') |
| 37 | + assert marks_for(document, "x") == [{"type": mark_type, "attrs": {"color": "blue"}}] |
| 38 | + |
| 39 | + |
| 40 | +@pytest.mark.parametrize( |
| 41 | + ("property_name", "mark_type"), |
| 42 | + [("color", "textStyle"), ("background-color", "highlight")], |
| 43 | +) |
| 44 | +def test_nested_style_overrides_without_leaking_to_siblings(property_name, mark_type): |
| 45 | + document = render( |
| 46 | + f'[before [inner]{{style="{property_name}:red"}} after]' |
| 47 | + f'{{style="{property_name}:blue"}} outside' |
| 48 | + ) |
| 49 | + for text in ("before ", " after"): |
| 50 | + assert marks_for(document, text) == [ |
| 51 | + {"type": mark_type, "attrs": {"color": "blue"}} |
| 52 | + ] |
| 53 | + assert marks_for(document, "inner") == [ |
| 54 | + {"type": mark_type, "attrs": {"color": "red"}} |
| 55 | + ] |
| 56 | + assert marks_for(document, " outside") == [] |
| 57 | + |
| 58 | + |
| 59 | +def test_nested_styles_preserve_other_marks_and_round_trip(): |
| 60 | + document = render( |
| 61 | + "[before [**bold** and [link](https://example.com) and `code`]" |
| 62 | + '{style="color:green; color:red"} after]' |
| 63 | + '{style="color:blue; background-color:yellow"}' |
| 64 | + ) |
| 65 | + expected_styles = { |
| 66 | + "textStyle": {"color": "red"}, |
| 67 | + "highlight": {"color": "yellow"}, |
| 68 | + } |
| 69 | + for text, extra_type in (("bold", "strong"), ("link", "link"), ("code", "code")): |
| 70 | + marks = marks_for(document, text) |
| 71 | + assert len(marks) == 3 |
| 72 | + assert { |
| 73 | + m["type"]: m["attrs"] for m in marks if "attrs" in m and m["type"] != "link" |
| 74 | + } == expected_styles |
| 75 | + assert any(m["type"] == extra_type for m in marks) |
| 76 | + assert next(m for m in marks_for(document, "link") if m["type"] == "link")[ |
| 77 | + "attrs" |
| 78 | + ] == {"href": "https://example.com"} |
| 79 | + |
| 80 | + markdown, unsupported = document_to_markdown(document) |
| 81 | + assert unsupported == [] |
| 82 | + assert markdown_to_doc(markdown) == document["content"] |
| 83 | + |
| 84 | + |
| 85 | +def test_three_levels_override_color_and_highlight_independently(): |
| 86 | + document = render( |
| 87 | + '[outer [middle [inner]{style="color:red"} middle-end]' |
| 88 | + '{style="background-color:green"} outer-end]' |
| 89 | + '{style="color:blue; background-color:yellow"}' |
| 90 | + ) |
| 91 | + for text, color, highlight in ( |
| 92 | + ("outer ", "blue", "yellow"), |
| 93 | + ("middle ", "blue", "green"), |
| 94 | + ("inner", "red", "green"), |
| 95 | + (" middle-end", "blue", "green"), |
| 96 | + (" outer-end", "blue", "yellow"), |
| 97 | + ): |
| 98 | + marks = marks_for(document, text) |
| 99 | + assert len(marks) == 2 |
| 100 | + assert {m["type"]: m["attrs"]["color"] for m in marks} == { |
| 101 | + "textStyle": color, |
| 102 | + "highlight": highlight, |
| 103 | + } |
| 104 | + |
| 105 | + |
| 106 | +def test_unrecognized_or_empty_inner_styles_keep_inherited_styles(): |
| 107 | + document = render( |
| 108 | + '[outer [inner]{style="color:; background-color:; font-weight:bold"}]' |
| 109 | + '{style="color:blue; background-color:yellow"}' |
| 110 | + ) |
| 111 | + assert document["content"][0]["content"] == [ |
| 112 | + { |
| 113 | + "type": "text", |
| 114 | + "text": "outer inner", |
| 115 | + "marks": [ |
| 116 | + {"type": "textStyle", "attrs": {"color": "blue"}}, |
| 117 | + {"type": "highlight", "attrs": {"color": "yellow"}}, |
| 118 | + ], |
| 119 | + } |
| 120 | + ] |
0 commit comments