+ @pytest.fixture
+ def string_io(self, const1, text=None):
+ return StringIO(text or const1)
+
+ @pytest.mark.massage
+ def test_do_massage_basic(self, const1, string_io, capsys):
+ def converter(
+ drafttext,
+ draftpath,
+ css,
+ related_to_html_only,
+ extensions,
+ tempdir,
+ ):
+ return Part("text", "plain", draftpath, orig=True)
+
+ do_massage(
+ draft_f=string_io,
+ draftpath=const1,
+ cmd_f=sys.stdout,
+ converter=converter,
+ )
+
+ captured = capsys.readouterr()
+ lines = captured.out.splitlines()
+ assert '="$my_editor"' in lines.pop(0)
+ assert '="$my_edit_headers"' in lines.pop(0)
+ assert "unset my_editor" == lines.pop(0)
+ assert "unset my_edit_headers" == lines.pop(0)
+ assert "send-message" in lines.pop(0)
+ assert "update-encoding" in lines.pop(0)
+ assert "first-entry" in lines.pop(0)
+ assert "source 'rm -f " in lines.pop(0)
+ assert "unset my_mdwn_postprocess_cmd_file" == lines.pop(0)
+
+ @pytest.mark.massage
+ def test_do_massage_fulltree(
+ self, string_io, const1, mime_tree_related_to_alternative, capsys
+ ):
+ def converter(
+ drafttext,
+ draftpath,
+ css,
+ related_to_html_only,
+ extensions,
+ tempdir,
+ ):
+ return mime_tree_related_to_alternative
+
+ max_attachments = 5
+ do_massage(
+ draft_f=string_io,
+ draftpath=const1,
+ cmd_f=sys.stdout,
+ max_other_attachments=max_attachments,
+ 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()
+ for i in range(max_attachments):
+ assert "move-up" in lines.pop()
+ assert "move-down" in lines.pop()
+ assert "HTML" in lines.pop()
+ assert "jump>1" in lines.pop()
+ assert "jump>2" in lines.pop()
+ assert "group-alternatives" in lines.pop()
+ assert "Alternative" in lines.pop()
+ assert "logo.png" in lines.pop()
+ assert "toggle-unlink" in lines.pop()
+ assert "content-id" in lines.pop()
+ for i in range(max_attachments):
+ assert "move-up" in lines.pop()
+ assert "move-down" in lines.pop()
+ assert "Logo" in lines.pop()
+ assert "jump>1" in lines.pop()
+ assert "jump>4" in lines.pop()
+ assert "group-related" in lines.pop()
+ assert "Related" in lines.pop()
+ assert "send-message" in lines.pop()
+ assert len(lines) == 0
+
+ @pytest.fixture
+ def fake_filewriter(self):
+ class FileWriter:
+ def __init__(self):
+ self._writes = []
+
+ def __call__(self, path, content, mode="w", **kwargs):
+ self._writes.append((path, content))
+
+ def pop(self, index=-1):
+ return self._writes.pop(index)
+
+ return FileWriter()
+
+ @pytest.fixture
+ def markdown_non_converter(self, const1, const2):
+ return lambda s, text: f"{const1}{text}{const2}"
+
+ @pytest.mark.converter
+ def test_converter_tree_basic(self, const1, const2, fake_filewriter):
+ path = pathlib.Path(const2)
+ tree = convert_markdown_to_html(
+ const1, path, filewriter_fn=fake_filewriter
+ )
+
+ assert tree.subtype == "alternative"
+ assert len(tree.children) == 2
+ assert tree.children[0].subtype == "plain"
+ assert tree.children[0].path == path
+ assert tree.children[0].orig
+ assert tree.children[1].subtype == "html"
+ assert tree.children[1].path == path.with_suffix(".html")
+
+ def test_converter_writes(
+ self,
+ const1,
+ const2,
+ fake_filewriter,
+ monkeypatch,
+ markdown_non_converter,
+ ):
+ path = pathlib.Path(const2)
+
+ with monkeypatch.context() as m:
+ m.setattr(markdown.Markdown, "convert", markdown_non_converter)
+ convert_markdown_to_html(
+ const1, path, filewriter_fn=fake_filewriter
+ )
+
+ assert (path, const1) == fake_filewriter.pop(0)
+ written = fake_filewriter.pop(0)
+ assert path.with_suffix(".html") == written[0]
+ assert const1 in written[1]
+
+ @pytest.mark.imgproc
+ def test_markdown_inline_image_processor(self):
+ imgpath1 = "file:/path/to/image.png"
+ imgpath2 = "file:///path/to/image.png?url=params"
+ imgpath3 = "/path/to/image.png"
+ text = f"""![inline local image]({imgpath1})
+ ![image inlined
+ with newline]({imgpath2})
+ ![image local path]({imgpath3})"""
+ text, html, images, mdwn = markdown_with_inline_image_support(text)
+
+ # local paths have been normalised to URLs:
+ imgpath3 = f"file://{imgpath3}"
+
+ assert 'src="cid:' in html
+ assert "](cid:" in text
+ assert len(images) == 3
+ assert imgpath1 in images
+ assert imgpath2 in images
+ assert imgpath3 in images
+ assert images[imgpath1].cid != images[imgpath2].cid
+ assert images[imgpath1].cid != images[imgpath3].cid
+ assert images[imgpath2].cid != images[imgpath3].cid
+
+ @pytest.mark.imgproc
+ def test_markdown_inline_image_processor_title_to_desc(self, const1):
+ imgpath = "file:///path/to/image.png"
+ text = f'![inline local image]({imgpath} "{const1}")'
+ text, html, images, mdwn = markdown_with_inline_image_support(text)
+ assert images[imgpath].desc == const1
+
+ @pytest.mark.imgproc
+ def test_markdown_inline_image_processor_alt_to_desc(self, const1):
+ imgpath = "file:///path/to/image.png"
+ text = f"![{const1}]({imgpath})"
+ text, html, images, mdwn = markdown_with_inline_image_support(text)
+ assert images[imgpath].desc == const1
+
+ @pytest.mark.imgproc
+ def test_markdown_inline_image_processor_title_over_alt_desc(
+ self, const1, const2
+ ):
+ imgpath = "file:///path/to/image.png"
+ text = f'![{const1}]({imgpath} "{const2}")'
+ text, html, images, mdwn = markdown_with_inline_image_support(text)
+ assert images[imgpath].desc == const2
+
+ @pytest.mark.imgproc
+ def test_markdown_inline_image_not_external(self):
+ imgpath = "https://path/to/image.png"
+ text = f"![inline image]({imgpath})"
+ text, html, images, mdwn = markdown_with_inline_image_support(text)
+
+ assert 'src="cid:' not in html
+ assert "](cid:" not in text
+ assert len(images) == 0
+
+ @pytest.mark.imgproc
+ def test_markdown_inline_image_local_file(self):
+ imgpath = "/path/to/image.png"
+ text = f"![inline image]({imgpath})"
+ text, html, images, mdwn = markdown_with_inline_image_support(text)
+
+ for k, v in images.items():
+ assert k == f"file://{imgpath}"
+ break
+
+ @pytest.mark.imgproc
+ def test_markdown_inline_image_expanduser(self):
+ imgpath = pathlib.Path("~/image.png")
+ text = f"![inline image]({imgpath})"
+ text, html, images, mdwn = markdown_with_inline_image_support(text)
+
+ for k, v in images.items():
+ assert k == f"file://{imgpath.expanduser()}"
+ break
+
+ @pytest.fixture
+ def test_png(self):
+ return (
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAE"
+ "AAAABCAAAAAA6fptVAAAACklEQVQI12P4DwABAQEAG7buVgAA"
+ )
+
+ @pytest.mark.imgproc
+ def test_markdown_inline_image_processor_base64(self, test_png):
+ text = f"![1px white inlined]({test_png})"
+ text, html, images, mdwn = markdown_with_inline_image_support(text)
+
+ assert 'src="cid:' in html
+ assert "](cid:" in text
+ assert len(images) == 1
+ assert test_png in images
+
+ @pytest.mark.converter
+ def test_converter_tree_inline_image_base64(
+ self, test_png, const1, fake_filewriter
+ ):
+ text = f"![inline base64 image]({test_png})"
+ path = pathlib.Path(const1)
+ tree = convert_markdown_to_html(
+ text,
+ path,
+ filewriter_fn=fake_filewriter,
+ related_to_html_only=False,
+ )
+ 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()
+
+ @pytest.mark.converter
+ def test_converter_tree_inline_image_base64_related_to_html(
+ self, test_png, const1, fake_filewriter
+ ):
+ text = f"![inline base64 image]({test_png})"
+ path = pathlib.Path(const1)
+ tree = convert_markdown_to_html(
+ text,
+ path,
+ filewriter_fn=fake_filewriter,
+ related_to_html_only=True,
+ )
+ assert tree.subtype == "alternative"
+ assert tree.children[1].subtype == "relative"
+ assert tree.children[1].children[1].subtype == "png"
+ written = fake_filewriter.pop()
+ assert tree.children[1].children[1].path == written[0]
+ assert written[1] == request.urlopen(test_png).read()
+
+ @pytest.mark.converter
+ def test_converter_tree_inline_image_cid(
+ self, const1, fake_filewriter
+ ):
+ text = f"![inline base64 image](cid:{const1})"
+ path = pathlib.Path(const1)
+ tree = convert_markdown_to_html(
+ text,
+ path,
+ filewriter_fn=fake_filewriter,
+ related_to_html_only=False,
+ )
+ assert len(tree.children) == 2
+ assert tree.children[0].cid != const1
+ assert tree.children[0].type != "image"
+ assert tree.children[1].cid != const1
+ assert tree.children[1].type != "image"
+
+ @pytest.mark.imgcoll
+ def test_inline_image_collection(
+ self, test_png, const1, const2, fake_filewriter
+ ):
+ test_images = {test_png: InlineImageInfo(cid=const1, desc=const2)}
+ relparts = collect_inline_images(
+ test_images, filewriter_fn=fake_filewriter
+ )
+
+ 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,