Skip to content

Commit 0bdb7a3

Browse files
authored
Merge pull request #34 from mshroyer/rst-refactor-tests
Refactor tests for RST support
2 parents 2b931b7 + 4ae9501 commit 0bdb7a3

File tree

2 files changed

+106
-126
lines changed

2 files changed

+106
-126
lines changed
Lines changed: 105 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Unit testing suite for the Graphviz plugin."""
22

33
# Copyright (C) 2015, 2021, 2023, 2025 Rafael Laboissière <[email protected]>
4+
# Copyright (C) 2025 Mark Shroyer <[email protected]>
45
#
56
# This program is free software: you can redistribute it and/or modify it
67
# under the terms of the GNU General Affero Public License as published by
@@ -21,231 +22,209 @@
2122
from tempfile import mkdtemp
2223
import unittest
2324

25+
from bs4 import BeautifulSoup, Tag
26+
2427
from pelican import Pelican
2528
from pelican.settings import read_settings
2629

2730
from . import graphviz
2831

2932
TEST_FILE_STEM = "test"
3033
TEST_DIR_PREFIX = "pelicantests."
31-
GRAPHVIZ_RE = (
32-
r'<{0} class="{1}"><img alt="{2}" '
33-
r'src="data:image/svg\+xml;base64,[0-9a-zA-Z+=]+"></{0}>'
34-
)
35-
36-
GRAPHVIZ_RE_XML = r'<svg width="\d+pt" height="\d+pt"'
34+
DIMENSION_ATTR_RE = re.compile(r"\d+pt")
3735

3836

3937
class TestGraphviz(unittest.TestCase):
4038
"""Class for testing the URL output of the Graphviz plugin."""
4139

4240
def setUp(
4341
self,
44-
block_start="..graphviz",
45-
image_class="graphviz",
46-
html_element="div",
47-
alt_text="GRAPH",
48-
compress=True,
49-
options=None,
50-
expected_html_element=None,
51-
expected_image_class=None,
52-
expected_alt_text=None,
53-
digraph_id="G",
42+
config=None,
43+
settings=None,
44+
expected=None,
5445
):
5546
"""Set up the test environment."""
5647
# Set the paths for the input (content) and output (html) files
5748
self.output_path = mkdtemp(prefix=TEST_DIR_PREFIX)
5849
self.content_path = mkdtemp(prefix=TEST_DIR_PREFIX)
5950

60-
# Configuration setting for the Pelican process
61-
settings = {
51+
# Input configuration
52+
self.config = {
53+
"md_block_start": "..graphviz",
54+
"options": None,
55+
"digraph_id": "G",
56+
}
57+
if config is not None:
58+
self.config.update(config)
59+
60+
# Settings for the Pelican process
61+
self.settings = {
6262
"PATH": self.content_path,
6363
"OUTPUT_PATH": self.output_path,
6464
"PLUGINS": [graphviz],
6565
"CACHE_CONTENT": False,
66-
"GRAPHVIZ_HTML_ELEMENT": html_element,
67-
"GRAPHVIZ_BLOCK_START": block_start,
68-
"GRAPHVIZ_IMAGE_CLASS": image_class,
69-
"GRAPHVIZ_ALT_TEXT": alt_text,
70-
"GRAPHVIZ_COMPRESS": compress,
7166
}
67+
if settings is not None:
68+
self.settings.update(settings)
69+
70+
# Properties of the expected output
71+
self.expected = {
72+
"compressed": True,
73+
"html_element": "div",
74+
"image_class": "graphviz",
75+
"alt_text": "G",
76+
}
77+
if expected is not None:
78+
self.expected.update(expected)
7279

73-
# Store the image_class and the html_element in self, since they will
74-
# be needed in the test_output method defined below
75-
self.image_class = image_class
76-
self.html_element = html_element
77-
self.alt_text = alt_text
78-
79-
# Get default expected values
80-
if not expected_image_class:
81-
self.expected_image_class = self.image_class
82-
else:
83-
self.expected_image_class = expected_image_class
84-
if not expected_html_element:
85-
self.expected_html_element = self.html_element
86-
else:
87-
self.expected_html_element = expected_html_element
88-
if not expected_alt_text:
89-
self.expected_alt_text = digraph_id if digraph_id else alt_text
90-
else:
91-
self.expected_alt_text = expected_alt_text
80+
def test_md(self):
81+
options_string = ""
82+
if self.config["options"]:
83+
kvs = ",".join(f'{k}="{v}"' for k, v in self.config["options"].items())
84+
options_string = f"[{kvs}]"
9285

9386
# Create the article file
9487
with open(os.path.join(self.content_path, f"{TEST_FILE_STEM}.md"), "w") as fid:
9588
# Write header
9689
fid.write(f"Title: {TEST_FILE_STEM}\nDate: 1970-01-01\n")
9790
# Write Graphviz block
98-
fid.write(
99-
f"""
100-
{block_start}{f" [{options}] " if options else " "}dot
101-
digraph {digraph_id if digraph_id else ""} {{
91+
fid.write(f"""
92+
{self.config["md_block_start"]} {options_string} dot
93+
digraph{f" {self.config['digraph_id']}" if self.config["digraph_id"] else ""} {{
10294
graph [rankdir = LR];
10395
Hello -> World
10496
}}
105-
"""
106-
)
97+
""")
10798

108-
# Run the Pelican instance
109-
self.settings = read_settings(override=settings)
110-
pelican = Pelican(settings=self.settings)
111-
pelican.run()
99+
self.run_pelican()
100+
self.assert_expected_output()
112101

113-
def tearDown(self):
114-
"""Tidy up the test environment."""
115-
rmtree(self.output_path)
116-
rmtree(self.content_path)
102+
def run_pelican(self):
103+
settings = read_settings(override=self.settings)
104+
pelican = Pelican(settings=settings)
105+
pelican.run()
117106

118-
def test_output(self):
107+
def assert_expected_output(self):
119108
"""Test for default values of the configuration variables."""
120109
# Open the output HTML file
121110
with open(os.path.join(self.output_path, f"{TEST_FILE_STEM}.html")) as fid:
111+
# Keep content as a string so we can see full content in output
112+
# from failed asserts.
122113
content = fid.read()
123-
found = False
124-
# Iterate over the lines and look for the HTML element corresponding
125-
# to the generated Graphviz figure
126-
for line in content.splitlines():
127-
if self.settings["GRAPHVIZ_COMPRESS"]:
128-
if re.search(
129-
GRAPHVIZ_RE.format(
130-
self.expected_html_element,
131-
self.expected_image_class,
132-
self.expected_alt_text,
133-
),
134-
line,
135-
):
136-
found = True
137-
break
138-
elif re.search(GRAPHVIZ_RE_XML, line):
139-
found = True
140-
break
141-
assert found, content
114+
soup = BeautifulSoup(content, "html.parser")
115+
if self.expected["compressed"]:
116+
elt = soup.find(
117+
self.expected["html_element"], class_=self.expected["image_class"]
118+
)
119+
assert isinstance(elt, Tag), content
120+
121+
img = elt.find("img", attrs={"alt": self.expected["alt_text"]})
122+
assert img is not None, content
123+
else:
124+
svg = soup.find("svg")
125+
assert isinstance(svg, Tag), content
126+
127+
for attr in ["width", "height"]:
128+
assert attr in svg.attrs
129+
assert DIMENSION_ATTR_RE.fullmatch(str(svg.attrs[attr]))
130+
131+
def tearDown(self):
132+
"""Tidy up the test environment."""
133+
rmtree(self.output_path)
134+
rmtree(self.content_path)
142135

143136

144137
class TestGraphvizHtmlElement(TestGraphviz):
145138
"""Class for exercising the configuration variable GRAPHVIZ_HTML_ELEMENT."""
146139

147140
def setUp(self):
148141
"""Initialize the configuration."""
149-
TestGraphviz.setUp(self, html_element="span")
150-
151-
def test_output(self):
152-
"""Test for GRAPHVIZ_HTML_ELEMENT setting."""
153-
TestGraphviz.test_output(self)
142+
value = "span"
143+
super().setUp(
144+
settings={"GRAPHVIZ_HTML_ELEMENT": value},
145+
expected={"html_element": value},
146+
)
154147

155148

156149
class TestGraphvizBlockStart(TestGraphviz):
157150
"""Class for exercising the configuration variable GRAPHVIZ_BLOCK_START."""
158151

159152
def setUp(self):
160153
"""Initialize the configuration."""
161-
TestGraphviz.setUp(self, block_start="==foobar")
162-
163-
def test_output(self):
164-
"""Test for GRAPHVIZ_BLOCK_START setting."""
165-
TestGraphviz.test_output(self)
154+
value = "==foobar"
155+
super().setUp(
156+
config={"md_block_start": value},
157+
settings={"GRAPHVIZ_BLOCK_START": value},
158+
)
166159

167160

168161
class TestGraphvizImageClass(TestGraphviz):
169162
"""Class for exercising configuration variable GRAPHVIZ_IMAGE_CLASS."""
170163

171164
def setUp(self):
172165
"""Initialize the configuration."""
173-
TestGraphviz.setUp(self, image_class="foo")
174-
175-
def test_output(self):
176-
"""Test for GRAPHVIZ_IMAGE_CLASS setting."""
177-
TestGraphviz.test_output(self)
166+
value = "foo"
167+
super().setUp(
168+
settings={"GRAPHVIZ_IMAGE_CLASS": value}, expected={"image_class": value}
169+
)
178170

179171

180172
class TestGraphvizImageNoCompress(TestGraphviz):
181173
"""Class for exercising configuration variable GRAPHVIZ_COMPRESS."""
182174

183175
def setUp(self):
184176
"""Initialize the configuration."""
185-
TestGraphviz.setUp(self, compress=False)
186-
187-
def test_output(self):
188-
"""Test for GRAPHVIZ_COMPRESS setting."""
189-
TestGraphviz.test_output(self)
177+
value = False
178+
super().setUp(
179+
settings={"GRAPHVIZ_COMPRESS": value}, expected={"compressed": value}
180+
)
190181

191182

192183
class TestGraphvizLocallyOverrideConfiguration(TestGraphviz):
193184
"""Class for exercising the override of a configuration variable."""
194185

195186
def setUp(self):
196187
"""Initialize the configuration."""
197-
TestGraphviz.setUp(
198-
self,
199-
html_element="div",
200-
options="html-element=span",
201-
expected_html_element="span",
188+
value = "span"
189+
super().setUp(
190+
config={"options": {"html-element": value}},
191+
expected={"html_element": value},
202192
)
203193

204-
def test_output(self):
205-
"""Test for overrind the configuration."""
206-
TestGraphviz.test_output(self)
207-
208194

209195
class TestGraphvizAltText(TestGraphviz):
210196
"""Class for exercising configuration variable GRAPHVIZ_ALT_TEXT."""
211197

212198
def setUp(self):
213199
"""Initialize the configuration."""
214-
TestGraphviz.setUp(self, alt_text="foo")
215-
216-
def test_output(self):
217-
"""Test for GRAPHVIZ_IMAGE_CLASS setting."""
218-
TestGraphviz.test_output(self)
200+
value = "G"
201+
super().setUp(
202+
config={"digraph_id": value},
203+
settings={"GRAPHVIZ_ALT_TEXT": "foo"},
204+
expected={"alt_text": value},
205+
)
219206

220207

221208
class TestGraphvizAltTextWithoutID(TestGraphviz):
222209
"""Class for testing the case where the Graphviz element has no id."""
223210

224211
def setUp(self):
225212
"""Initialize the configuration."""
226-
TestGraphviz.setUp(
227-
self,
228-
digraph_id=None,
229-
alt_text="foo",
213+
value = "foo"
214+
super().setUp(
215+
config={"digraph_id": None},
216+
settings={"GRAPHVIZ_ALT_TEXT": value},
217+
expected={"alt_text": value},
230218
)
231219

232-
def test_output(self):
233-
"""Test for GRAPHVIZ_IMAGE_CLASS setting."""
234-
TestGraphviz.test_output(self)
235-
236220

237221
class TestGraphvizAltTextViaOption(TestGraphviz):
238222
"""Class for testing the alternative text given via the alt-text option."""
239223

240224
def setUp(self):
241225
"""Initialize the configuration."""
242226
text = "A wonderful graph"
243-
TestGraphviz.setUp(
244-
self,
245-
options=f'alt-text="{text}"',
246-
expected_alt_text=text,
227+
super().setUp(
228+
config={"options": {"alt-text": text}},
229+
expected={"alt_text": text},
247230
)
248-
249-
def test_output(self):
250-
"""Test for GRAPHVIZ_IMAGE_CLASS setting."""
251-
TestGraphviz.test_output(self)

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ lint = [
4545
"ruff>=0.9.1,<1.0.0",
4646
]
4747
test = [
48+
"beautifulsoup4>=4.13",
4849
"invoke>=2.2",
4950
"markdown>=3.4",
5051
"pytest>=7.0",

0 commit comments

Comments
 (0)