+
+ written = fake_filewriter.pop()
+ assert b"PNG" in written[1]
+
+ assert relparts[0].subtype == "png"
+ assert relparts[0].path == written[0]
+ assert relparts[0].cid == const1
+ assert relparts[0].desc.endswith(const2)
+
+ if _PYNLINER:
+
+ @pytest.mark.styling
+ def test_apply_stylesheet(self):
+ html = "<p>Hello, world!</p>"
+ css = "p { color:red }"
+ out = apply_styling(html, css)
+ assert 'p style="color' in out
+
+ @pytest.mark.styling
+ def test_massage_styling_to_converter(self, string_io, const1):
+ css = "p { color:red }"
+ css_f = StringIO(css)
+ out_f = StringIO()
+ css_applied = []
+
+ def converter(
+ drafttext,
+ draftpath,
+ css,
+ related_to_html_only,
+ extensions,
+ tempdir,
+ ):
+ css_applied.append(css)
+ return Part("text", "plain", draftpath, orig=True)
+
+ do_massage(
+ draft_f=string_io,
+ draftpath=const1,
+ cmd_f=out_f,
+ css_f=css_f,
+ converter=converter,
+ )
+ assert css_applied[0] == css
+
+ @pytest.mark.converter
+ def test_converter_apply_styles(
+ self, const1, fake_filewriter, monkeypatch
+ ):
+ path = pathlib.Path(const1)
+ text = "Hello, world!"
+ css = "p { color:red }"
+ with monkeypatch.context() as m:
+ m.setattr(
+ markdown.Markdown,
+ "convert",
+ lambda s, t: f"<p>{t}</p>",
+ )
+ convert_markdown_to_html(
+ text, path, css=css, filewriter_fn=fake_filewriter
+ )
+ assert "color: red" in fake_filewriter.pop()[1]
+
+ if _PYGMENTS_CSS:
+
+ @pytest.mark.styling
+ def test_apply_stylesheet_pygments(self):
+ html = (
+ f'<div class="{_CODEHILITE_CLASS}">'
+ "<pre>def foo():\n return</pre></div>"
+ )
+ out = apply_styling(html, _PYGMENTS_CSS)
+ assert f'{_CODEHILITE_CLASS}" style="' in out
+
+ @pytest.mark.massage
+ def test_mime_tree_relative_within_alternative(
+ self, string_io, const1, capsys, mime_tree_related_to_html
+ ):
+ def converter(
+ drafttext,
+ draftpath,
+ css,
+ related_to_html_only,
+ extensions,
+ tempdir,
+ ):
+ return mime_tree_related_to_html
+
+ do_massage(
+ draft_f=string_io,
+ draftpath=const1,
+ cmd_f=sys.stdout,
+ converter=converter,
+ )
+
+ captured = capsys.readouterr()
+ lines = captured.out.splitlines()[4:-2]
+ assert "first-entry" in lines.pop()
+ assert "update-encoding" in lines.pop()
+ assert "Plain" in lines.pop()
+ assert "part.html" in lines.pop()
+ assert "toggle-unlink" in lines.pop()
+ assert "move-up" in lines.pop()
+ while True:
+ top = lines.pop()
+ if "move-up" not in top:
+ break
+ assert "move-down" in top
+ assert "HTML" in lines.pop()
+ assert "logo.png" in lines.pop()
+ assert "toggle-unlink" in lines.pop()
+ assert "content-id" in lines.pop()
+ assert "move-up" in lines.pop()
+ while True:
+ top = lines.pop()
+ if "move-up" not in top:
+ break
+ assert "move-down" in top
+ assert "move-down" in lines.pop()
+ assert "Logo" in lines.pop()
+ assert "jump>2" in lines.pop()
+ assert "jump>3" in lines.pop()
+ assert "group-related" in lines.pop()
+ assert "Related" in lines.pop()
+ assert "jump>1" in lines.pop()
+ assert "jump>2" in lines.pop()
+ assert "group-alternative" in lines.pop()
+ assert "Alternative" in lines.pop()
+ assert "send-message" in lines.pop()
+ assert len(lines) == 0
+
+ @pytest.mark.massage
+ def test_mime_tree_nested_trees_does_not_break_positioning(
+ self, string_io, const1, capsys
+ ):
+ def converter(
+ drafttext,
+ draftpath,
+ css,
+ related_to_html_only,
+ extensions,
+ tempdir,
+ ):
+ return Multipart(
+ "relative",
+ children=[
+ Multipart(
+ "alternative",
+ children=[
+ Part(
+ "text",
+ "plain",
+ "part.txt",
+ desc="Plain",
+ orig=True,
+ ),
+ Multipart(
+ "alternative",
+ children=[
+ Part(
+ "text",
+ "plain",
+ "part.txt",
+ desc="Nested plain",
+ ),
+ Part(
+ "text",
+ "html",
+ "part.html",
+ desc="Nested HTML",
+ ),
+ ],
+ desc="Nested alternative",
+ ),
+ ],
+ desc="Alternative",
+ ),
+ Part(
+ "text",
+ "png",
+ "logo.png",
+ cid="logo.png",
+ desc="Logo",
+ ),
+ ],
+ desc="Related",
+ )
+
+ do_massage(
+ draft_f=string_io,
+ draftpath=const1,
+ cmd_f=sys.stdout,
+ converter=converter,
+ )
+
+ captured = capsys.readouterr()
+ lines = captured.out.splitlines()
+ while "logo.png" not in lines.pop():
+ pass
+ lines.pop()
+ assert "content-id" in lines.pop()
+ assert "move-up" in lines.pop()
+ while True:
+ top = lines.pop()
+ if "move-up" not in top:
+ break
+ assert "move-down" in top
+ # Due to the nested trees, the number of descendents of the sibling
+ # actually needs to be considered, not just the nieces. So to move
+ # from position 1 to position 6, it only needs one <move-down>
+ # because that jumps over the entire sibling tree. Thus what
+ # follows next must not be another <move-down>
+ assert "Logo" in lines.pop()
+
+ @pytest.mark.sig
+ def test_signature_extraction_no_signature(self, const1):
+ assert (const1, None, None) == extract_signature(const1)
+
+ @pytest.mark.sig
+ def test_signature_extraction_just_text(self, const1, const2):
+ origtext, textsig, htmlsig = extract_signature(
+ f"{const1}{EMAIL_SIG_SEP}{const2}"
+ )
+ assert origtext == const1
+ assert textsig == const2
+ assert htmlsig is None
+
+ @pytest.mark.sig
+ def test_signature_extraction_html(self, const1, const2):
+ path = pathlib.Path("somepath")
+ sigconst = "HTML signature from {path} but as a string"
+
+ def filereader_fn(path):
+ return (
+ f'<div id="signature">{sigconst.format(path=path)}</div>'
+ )
+
+ origtext, textsig, htmlsig = extract_signature(
+ f"{const1}{EMAIL_SIG_SEP}{HTML_SIG_MARKER} {path}\n{const2}",
+ filereader_fn=filereader_fn,
+ )
+ assert origtext == const1
+ assert textsig == const2
+ assert htmlsig == sigconst.format(path=path)
+
+ @pytest.mark.sig
+ def test_signature_extraction_file_not_found(self, const1):
+ path = pathlib.Path("/does/not/exist")
+ with pytest.raises(FileNotFoundError):
+ origtext, textsig, htmlsig = extract_signature(
+ f"{const1}{EMAIL_SIG_SEP}{HTML_SIG_MARKER}{path}\n{const1}"
+ )
+
+ @pytest.mark.imgproc
+ def test_image_registry(self, const1):
+ reg = ImageRegistry()
+ cid = reg.register(const1)
+ assert "@" in cid
+ assert not cid.startswith("<")
+ assert not cid.endswith(">")
+ assert const1 in reg
+
+ @pytest.mark.imgproc
+ def test_image_registry_file_uri(self, const1):
+ reg = ImageRegistry()
+ reg.register("/some/path")
+ for path in reg:
+ assert path.startswith("file://")
+ break
+
+ @pytest.mark.converter
+ @pytest.mark.sig
+ def test_converter_signature_handling(
+ self, const1, fake_filewriter, monkeypatch
+ ):
+ path = pathlib.Path(const1)
+
+ mailparts = (
+ "This is the mail body\n",
+ f"{EMAIL_SIG_SEP}",
+ "This is a plain-text signature only",
+ )
+
+ def filereader_fn(path):
+ return ""
+
+ with monkeypatch.context() as m:
+ m.setattr(markdown.Markdown, "convert", lambda s, t: t)
+ convert_markdown_to_html(
+ "".join(mailparts),
+ path,
+ filewriter_fn=fake_filewriter,
+ filereader_fn=filereader_fn,
+ )
+
+ soup = bs4.BeautifulSoup(fake_filewriter.pop()[1], "html.parser")
+ body = soup.body.contents
+
+ assert mailparts[0] in body.pop(0)
+
+ sig = soup.select_one("#signature")
+ assert sig == body.pop(0)
+
+ sep = sig.select_one("span.sig_separator")
+ assert sep == sig.contents[0]
+ assert f"\n{sep.text}\n" == EMAIL_SIG_SEP
+
+ assert mailparts[2] in sig.contents[1]
+
+ @pytest.mark.converter
+ @pytest.mark.sig
+ def test_converter_signature_handling_htmlsig(
+ self, const1, fake_filewriter, monkeypatch
+ ):
+ path = pathlib.Path(const1)
+
+ mailparts = (
+ "This is the mail body",
+ f"{EMAIL_SIG_SEP}",
+ f"{HTML_SIG_MARKER}{path}\n",
+ "This is the plain-text version",
+ )
+
+ htmlsig = "HTML Signature from {path}"
+
+ def filereader_fn(path):
+ return f'<div id="signature">{htmlsig.format(path=path)}</div>'
+
+ def mdwn_fn(t):
+ return t.upper()
+
+ with monkeypatch.context() as m:
+ m.setattr(
+ markdown.Markdown, "convert", lambda s, t: mdwn_fn(t)
+ )
+ convert_markdown_to_html(
+ "".join(mailparts),
+ path,
+ filewriter_fn=fake_filewriter,
+ filereader_fn=filereader_fn,
+ )
+
+ soup = bs4.BeautifulSoup(fake_filewriter.pop()[1], "html.parser")
+ sig = soup.select_one("#signature")
+ sig.span.extract()
+
+ assert HTML_SIG_MARKER not in sig.text
+ assert htmlsig.format(path=path) == sig.text.strip()
+
+ plaintext = fake_filewriter.pop()[1]
+ assert plaintext.endswith(EMAIL_SIG_SEP + mailparts[-1])
+
+ @pytest.mark.converter
+ @pytest.mark.sig
+ def test_converter_signature_handling_htmlsig_with_image(
+ self, const1, fake_filewriter, monkeypatch, test_png
+ ):
+ path = pathlib.Path(const1)
+
+ mailparts = (
+ "This is the mail body",
+ f"{EMAIL_SIG_SEP}",
+ f"{HTML_SIG_MARKER}{path}\n",
+ "This is the plain-text version",
+ )
+
+ htmlsig = (
+ "HTML Signature from {path} with image\n"
+ f'<img src="{test_png}">\n'
+ )
+
+ def filereader_fn(path):
+ return f'<div id="signature">{htmlsig.format(path=path)}</div>'
+
+ def mdwn_fn(t):
+ return t.upper()
+
+ with monkeypatch.context() as m:
+ m.setattr(
+ markdown.Markdown, "convert", lambda s, t: mdwn_fn(t)
+ )
+ convert_markdown_to_html(
+ "".join(mailparts),
+ path,
+ filewriter_fn=fake_filewriter,
+ filereader_fn=filereader_fn,
+ )
+
+ assert fake_filewriter.pop()[0].suffix == ".png"
+
+ soup = bs4.BeautifulSoup(fake_filewriter.pop()[1], "html.parser")
+ assert soup.img.attrs["src"].startswith("cid:")
+
+ @pytest.mark.converter
+ @pytest.mark.sig
+ def test_converter_signature_handling_textsig_with_image(
+ self, const1, fake_filewriter, test_png
+ ):
+ mailparts = (
+ "This is the mail body",
+ f"{EMAIL_SIG_SEP}",
+ "This is the plain-text version with image\n",
+ f"![Inline]({test_png})",
+
+ )
+ tree = convert_markdown_to_html
+ "".join(mailparts),
+ pathlib.Path(const1),
+ filewriter_fn=fake_filewriter,
+ )
+
+ assert tree.subtype == "relative"
+ assert tree.children[0].subtype == "alternative"
+ assert tree.children[1].subtype == "png"
+ written = fake_filewriter.pop()
+ assert tree.children[1].path == written[0]
+ assert written[1] == request.urlopen(test_png).read()
+
+ def test_converter_attribution_to_admonition(self, fake_filewriter):
+