releases/tests/presentation.py
Jeff Forcier 856d91e0f5 Update construct_releases() to yield its LineManager too
Then expose that in util.parse_changelog()
2016-10-10 11:10:13 -07:00

258 lines
9.1 KiB
Python

from spec import Spec, eq_
from docutils.nodes import (
reference, bullet_list, list_item, literal, raw, paragraph, Text
)
from releases import (
Issue,
construct_releases,
construct_nodes,
)
from _util import (
b, f, s,
entry,
make_app,
release,
releases,
setup_issues,
)
def _obj2name(obj):
cls = obj if isinstance(obj, type) else obj.__class__
return cls.__name__.split('.')[-1]
def _expect_type(node, cls):
type_ = _obj2name(node)
name = _obj2name(cls)
msg = "Expected %r to be a %s, but it's a %s" % (node, name, type_)
assert isinstance(node, cls), msg
class presentation(Spec):
"""
Expansion/extension of docutils nodes (rendering)
"""
def setup(self):
setup_issues(self)
def _generate(self, *entries, **kwargs):
raw = kwargs.pop('raw', False)
nodes = construct_nodes(releases(*entries, **kwargs))
# By default, yield the contents of the bullet list.
return nodes if raw else nodes[0][1][0]
def _test_link(self, kwargs, type_, expected):
app = make_app(**kwargs)
nodes = construct_nodes(construct_releases([
release('1.0.2', app=app),
entry(b(15, app=app)),
release('1.0.0'),
], app=app)[0])
# Shorthand for "I'll do my own asserts"
if expected is None:
return nodes
if type_ == 'release':
header = nodes[0][0][0].astext()
assert expected in header
elif type_ == 'issue':
link = nodes[0][1][0][0][2]
eq_(link['refuri'], expected)
else:
raise Exception("Gave unknown type_ kwarg to _test_link()!")
def issues_with_numbers_appear_as_number_links(self):
self._test_link({}, 'issue', 'bar_15')
def releases_appear_as_header_links(self):
self._test_link({}, 'release', 'foo_1.0.2')
def links_will_use_github_option_if_defined(self):
kwargs = {
'release_uri': None,
'issue_uri': None,
'github_path': 'foo/bar',
}
for type_, expected in (
('issue', 'https://github.com/foo/bar/issues/15'),
('release', 'https://github.com/foo/bar/tree/1.0.2'),
):
self._test_link(kwargs, type_, expected)
def issue_links_prefer_explicit_setting_over_github_setting(self):
kwargs = {
'release_uri': None,
'issue_uri': 'explicit_issue_%s',
'github_path': 'foo/bar',
}
self._test_link(kwargs, 'issue', 'explicit_issue_15')
def release_links_prefer_explicit_setting_over_github_setting(self):
kwargs = {
'release_uri': 'explicit_release_%s',
'issue_uri': None,
'github_path': 'foo/bar',
}
self._test_link(kwargs, 'release', 'explicit_release_1.0.2')
def completely_blank_uri_settings_does_not_asplode(self):
kwargs = {
'release_uri': None,
'issue_uri': None,
'github_path': None,
}
# Get nodes for direct inspection
nodes = self._test_link(kwargs, 'release', None)
# Ensure release entry still displays release version.
# (These are curently constructed as raw text nodes so no other great
# way to test this. Meh.)
text = nodes[0][0][0].astext()
assert '>1.0.2 <span' in text
# Ensure issues still display issue number. (Ditto.)
text = nodes[0][1][0][0].astext()
assert '>Bug</span>] #15:' in text
def _assert_prefix(self, entries, expectation):
assert expectation in self._generate(*entries)[0][0][0]
def bugs_marked_as_bugs(self):
self._assert_prefix(['1.0.2', self.b], 'Bug')
def features_marked_as_features(self):
self._assert_prefix(['1.1.0', self.f], 'Feature')
def support_marked_as_support(self):
self._assert_prefix(['1.1.0', self.s], 'Support')
def dashed_issues_appear_as_unlinked_issues(self):
node = self._generate('1.0.2', b('-'))
assert not isinstance(node[0][2], reference)
def zeroed_issues_appear_as_unlinked_issues(self):
node = self._generate('1.0.2', b(0))
assert not isinstance(node[0][2], reference)
def un_prefixed_list_items_appear_as_unlinked_bugs(self):
fake = list_item('', paragraph('', '',
Text("fixes an issue in "), literal('', 'methodname')))
node = self._generate('1.0.2', fake)
# [<raw prefix>, <inline colon>, <inline space>, <text>, <monospace>]
eq_(len(node[0]), 5)
assert 'Bug' in str(node[0][0])
assert 'fixes an issue' in str(node[0][3])
assert 'methodname' in str(node[0][4])
def un_prefixed_list_items_get_no_prefix_under_unstable_prehistory(self):
app = make_app(unstable_prehistory=True)
fake = list_item('', paragraph('', '', raw('', 'whatever')))
node = self._generate('0.1.0', fake, app=app, skip_initial=True)
# [<raw bug text>]
eq_(len(node[0]), 1)
assert 'Bug' not in str(node[0][0])
assert 'whatever' in str(node[0][0])
def issues_remain_wrapped_in_unordered_list_nodes(self):
node = self._generate('1.0.2', self.b, raw=True)[0][1]
_expect_type(node, bullet_list)
_expect_type(node[0], list_item)
def release_headers_have_local_style_tweaks(self):
node = self._generate('1.0.2', self.b, raw=True)[0][0]
_expect_type(node, raw)
# Header w/ bottom margin
assert '<h2 style="margin-bottom' in str(node)
# Date span w/ font-size
assert '<span style="font-size' in str(node)
def descriptions_are_preserved(self):
# Changelog containing an issue item w/ trailing node
issue = list_item('',
paragraph('', '', self.b.deepcopy(), raw('', 'x')),
)
# Trailing nodes should appear post-processing after the link/etc
rest = self._generate('1.0.2', issue)[0]
eq_(len(rest), 5)
_expect_type(rest[4], raw)
eq_(rest[4].astext(), 'x')
def complex_descriptions_are_preserved(self):
# Complex 'entry' mapping to an outer list_item (list) containing two
# paragraphs, one w/ the real issue + desc, another simply a 2nd text
# paragraph. Using 'raw' nodes for filler as needed.
issue = list_item('',
paragraph('', '', self.b.deepcopy(), raw('', 'x')),
paragraph('', '', raw('', 'y')),
)
li = self._generate('1.0.2', issue)
# Expect that the machinery parsing issue nodes/nodelists, is not
# discarding our 2nd 'paragraph'
eq_(len(li), 2)
p1, p2 = li
# Last item in 1st para is our 1st raw node
_expect_type(p1[4], raw)
eq_(p1[4].astext(), 'x')
# Only item in 2nd para is our 2nd raw node
_expect_type(p2[0], raw)
eq_(p2[0].astext(), 'y')
def descriptions_are_parsed_for_issue_roles(self):
item = list_item('',
paragraph('', '', self.b.deepcopy(), s(5))
)
para = self._generate('1.0.2', item)[0]
# Sanity check - in a broken parsing scenarion, the 4th child will be a
# raw issue object
assert not isinstance(para[4], Issue)
# First/primary link
_expect_type(para[2], reference)
eq_(para[2].astext(), '#15')
assert 'Bug' in para[0].astext()
# Second/inline link
_expect_type(para[6], reference)
eq_(para[6].astext(), '#5')
assert 'Support' in para[4].astext()
def unreleased_buckets_omit_major_version_when_only_one_exists(self):
result = self._generate(b(1), raw=True)[0][0][0]
html = str(result) # since repr() from test-fail hides actual text
assert "Next bugfix release" in html
def unreleased_buckets_display_major_version_when_multiple(self):
entries = (
b(3), # should appear in unreleased bugs for 2.x
'2.0.0',
b(2), # should appear in unreleased bugs for 1.x
'1.0.1',
b(1),
)
# Expectation: [2.x unreleased, 1.x unreleased, 1.0.1]
two_x, one_x, _ = self._generate(*entries, raw=True)
html = str(two_x[0][0])
assert "Next 2.x bugfix release" in html
html = str(one_x[0][0])
assert "Next 1.x bugfix release" in html
def unreleased_displays_version_when_only_some_lines_displayed(self):
# I.e. if there's unreleased 1.x stuff but no unreleased 2.x, still
# display the "1.x".
entries = (
# Note lack of any bugfixes post 2.0.0
'2.0.0',
b(2),
'1.0.1',
b(1),
)
# Expectation: [1.x unreleased, 1.0.1] - no 2.x.
result = self._generate(*entries, raw=True)
eq_(len(result), 2)
html = str(result[0][0][0])
assert "Next 1.x bugfix release" in html
def unstable_prehistory_active_means_only_one_unreleased_release(self):
app = make_app(unstable_prehistory=True)
entries = (b(2), f(3), '0.1.0', b(1))
result = self._generate(*entries, app=app, raw=True, skip_initial=True)
html = str(result[0][0][0])
assert "Next release" in html