1
# Copyright 2010 Canonical Ltd. This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
4
"""Tests for `BaseSeriesTemplatesView` and descendants."""
10
from zope.security.proxy import removeSecurityProxy
12
from canonical.launchpad.ftests import login
13
from canonical.launchpad.webapp.publisher import canonical_url
14
from canonical.launchpad.webapp.servers import LaunchpadTestRequest
15
from canonical.testing import DatabaseFunctionalLayer
16
from lp.registry.model.distroseries import DistroSeries
17
from lp.registry.model.productseries import ProductSeries
18
from lp.translations.browser.distroseries import DistroSeriesTemplatesView
19
from lp.translations.browser.productseries import ProductSeriesTemplatesView
20
from lp.testing import (
24
from lp.testing.sampledata import ADMIN_EMAIL
27
class SeriesTemplatesViewScenario:
29
# The view class to test.
32
# The expected list of columns for the view_class, each shown as a
33
# list holding the column's CSS class.
36
def makeTemplateContext(self):
37
"""Create makePOTemplate arguments to create a series."""
38
raise NotImplementedError()
40
def _makeTemplate(self, **kwargs):
41
"""Create a distro- or productseries for the view."""
42
args_dict = self.makeTemplateContext()
43
args_dict.update(kwargs)
44
return self.factory.makePOTemplate(**args_dict)
46
def _getSeries(self, template):
47
"""Get `template`'s distro- or productseries."""
48
return template.distroseries or template.productseries
50
def _makeView(self, template=None):
51
"""Create a `BaseTemplatesView` containing `template`."""
53
template = self._makeTemplate()
54
request = LaunchpadTestRequest()
55
view = self.view_class(self._getSeries(template), request)
59
def _findTagClasses(self, html, tag):
60
"""Find the CSS classes for all instances of `tag`s in `html`.
62
Returns a list of lists. The outer list represents instances of
63
`tag`, in the order in which they are found. The inner lists
64
hold the respective sets of HTML classes for these tags, sorted
67
regex = '<%s [^>]*class="([^"]*)"' % tag
69
sorted(css_class.split())
70
for css_class in re.findall(regex, html)]
72
def _findActions(self, html):
73
"""Find the available actions in an HTML actions column."""
74
return re.findall('<[^>]*>([^<]*)</[^>]*', html)
76
def test_has_the_right_columns(self):
77
# Test the column headers against the expected list.
78
view = self._makeView()
79
header = view.renderTemplatesHeader()
80
self.assertEqual(self.columns, self._findTagClasses(header, 'th'))
82
def test_logging_in_adds_actions_column(self):
83
# A logged-in user gets to see an extra "actions" column.
84
template = self._makeTemplate()
85
login_person(self.factory.makePerson())
86
view = self._makeView(template)
87
columns = self.columns + [['actions_column']]
88
header = view.renderTemplatesHeader()
89
self.assertEqual(columns, self._findTagClasses(header, 'th'))
90
row = view.renderTemplateRow(template)
91
self.assertEqual(columns, self._findTagClasses(row, 'td'))
93
def test_user_actions(self):
94
# The only action offered to regular users is Download.
95
template = self._makeTemplate()
96
url = canonical_url(template)
97
login_person(self.factory.makePerson())
98
view = self._makeView(template)
102
self._findActions(view._renderActionsColumn(template, url)))
104
def test_admin_actions(self):
105
# An administrator gets to see all actions on a template.
106
template = self._makeTemplate()
107
url = canonical_url(template)
109
view = self._makeView(template)
112
['Edit', 'Upload', 'Download', 'Administer'],
113
self._findActions(view._renderActionsColumn(template, url)))
115
def test_edit_actions(self):
116
# A non-admin user with edit rights gets the Edit, Upload, and
118
template = self._makeTemplate()
119
url = canonical_url(template)
120
login_person(self.factory.makePerson())
121
view = self._makeView(template)
125
['Edit', 'Upload', 'Download'],
126
self._findActions(view._renderActionsColumn(template, url)))
128
def test_constructs_correct_urls(self):
129
# The view classes can override constructTemplateURL with
130
# optimized versions. These can produce either an absolute URL
131
# that exactly matches the template's canonical_url, or a
132
# relative one starting from the series' canonical_url.
133
template = self._makeTemplate()
134
view = self._makeView(template)
136
series_url = canonical_url(
137
self._getSeries(template), rootsite='translations')
138
constructed_url = view.constructTemplateURL(template)
141
canonical_url(template),
142
(constructed_url, '/'.join([series_url, constructed_url])))
144
def test_renderTemplateLink(self):
145
# _renderTemplateLink renders a link to the template.
146
template = self._makeTemplate()
147
view = self._makeView(template)
149
url = view.constructTemplateURL(template)
150
link = view._renderTemplateLink(template, url)
152
self.assertIn('<a ', link)
153
self.assertIn('href="%s"' % url, link)
154
self.assertIn('>%s<' % template.name, link)
156
def test_renderTemplateLink_marks_disabled(self):
157
# _renderTemplateLinks marks disabled templates as "(inactive)."
158
template = self._makeTemplate()
159
view = self._makeView(template)
160
url = canonical_url(template)
162
removeSecurityProxy(template).iscurrent = True
164
'(inactive)', view._renderTemplateLink(template, url))
165
removeSecurityProxy(template).iscurrent = False
166
self.assertIn('(inactive)', view._renderTemplateLink(template, url))
168
def test_renderLastUpdateDate_sets_sortkey(self):
169
# _renderLastUpdateDate sets the full date as the column's sort
170
# key, so that clicking on the column header sorts by date (even
171
# if sorting alphabetically by the visible date might produce a
172
# different ordering).
173
template = self._makeTemplate()
174
view = self._makeView(template)
176
date_field = view._renderLastUpdateDate(template)
178
# The sort key is set in a span of class "sortkey."
179
sortkey_match = re.findall(
180
'<span class="sortkey">([^<]*)</span>', date_field)
181
self.assertIsNot(None, sortkey_match)
182
self.assertEqual(1, len(sortkey_match))
184
# The column also has the same full date as a tooltip.
185
full_date = sortkey_match[0].strip()
186
self.assertIn('title="%s"' % full_date, date_field)
188
def test_renderAction_returns_empty_string_if_not_enabled(self):
189
view = self._makeView()
192
view._renderAction('url', 'name', 'path', 'sprite', False))
194
def test_renderAction(self):
195
# If enabled, _renderAction produces a link to an action form
196
# for a given template.
197
view = self._makeView()
199
url = self.factory.getUniqueString()
200
name = self.factory.getUniqueString()
201
path = self.factory.getUniqueString()
202
sprite = self.factory.getUniqueString()
204
action = view._renderAction(url, name, path, sprite, True)
206
self.assertIn('<a ', action)
207
self.assertIn('href="%s/%s"' % (url, path), action)
208
self.assertIn(name, action)
209
self.assertIn('class="sprite %s"' % sprite, action)
211
def test_renderField_returns_empty_string_if_no_content(self):
212
view = self._makeView()
213
self.assertEqual('', view._renderField('x', None, tag='y'))
215
def test_renderField_returns_empty_field_for_empty_content(self):
216
field = self._makeView()._renderField('class', '', tag='tag')
217
self.assertIn('<tag class="class">', field)
218
self.assertIn('</tag>', field)
220
def test_renderField(self):
221
column_class = self.factory.getUniqueString()
222
content = self.factory.getUniqueString()
223
tag = self.factory.getUniqueString()
225
field = self._makeView()._renderField(column_class, content, tag=tag)
227
self.assertIn('<%s class="%s">' % (tag, column_class), field)
228
self.assertIn(content, field)
229
self.assertIn('</%s>' % tag, field)
231
def test_renderTemplateRow(self):
232
template = self._makeTemplate()
233
view = self._makeView(template)
235
row = view.renderTemplateRow(template)
238
[sorted(['template_row', view.rowCSSClass(template)])],
239
self._findTagClasses(row, 'tr'))
241
self.assertEqual(self.columns, self._findTagClasses(row, 'td'))
244
class TestDistroSeriesTemplatesView(SeriesTemplatesViewScenario,
245
TestCaseWithFactory):
246
"""Run the test scenario against `DistroSeriesTemplatesView`."""
248
layer = DatabaseFunctionalLayer
250
view_class = DistroSeriesTemplatesView
254
['sourcepackage_column'],
257
['lastupdate_column'],
260
def makeTemplateContext(self):
261
"""See `SeriesTemplatesViewScenario`."""
263
sourcepackagename=self.factory.makeSourcePackageName(),
264
distroseries=self.factory.makeDistroSeries())
266
def test_makeTemplate(self):
267
# In this test case, _makeTemplate produces a distroseries
269
template = self._makeTemplate()
270
self.assertIsInstance(template.distroseries, DistroSeries)
271
self.assertIs(None, template.productseries)
273
def test_findTagClasses(self):
274
# Tested here arbitrarily (no need to repeat it): the
275
# _findTagClasses helper.
278
self._findTagClasses('<x class="c b" /><x class="a">', 'x'))
280
def test_findActions(self):
281
# Tested here arbitrarily (no need to repeat it): the
282
# _findActions helper.
283
self.assertEqual(['Foo'], self._findActions('<a class="bar">Foo</a>'))
285
def test_is_distroseries(self):
286
self.assertTrue(self._makeView().is_distroseries)
288
def test_renderSourcePackage(self):
289
# _renderSourcePackage returns the template's source-package
290
# name for a productseries view.
291
template = self._makeTemplate()
292
view = self._makeView(template)
294
return self.assertEqual(
295
template.sourcepackagename.name,
296
view._renderSourcePackage(template))
299
class TestProductSeriesTemplatesView(SeriesTemplatesViewScenario,
300
TestCaseWithFactory):
301
"""Run the test scenario against `ProductSeriesTemplatesView`."""
303
layer = DatabaseFunctionalLayer
305
view_class = ProductSeriesTemplatesView
311
['lastupdate_column'],
314
def makeTemplateContext(self):
315
"""See `SeriesTemplatesViewScenario`."""
316
return dict(productseries=self.factory.makeProductSeries())
318
def test_makeTemplate(self):
319
# In this test case, _makeTemplate produces a productseries
321
template = self._makeTemplate()
322
self.assertIs(None, template.distroseries)
323
self.assertIsInstance(template.productseries, ProductSeries)
325
def test_is_distroseries(self):
326
self.assertFalse(self._makeView().is_distroseries)
328
def test_renderSourcePackage(self):
329
# _renderSourcePackage returns None for a productseries view.
330
template = self._makeTemplate()
331
view = self._makeView(template)
332
self.assertIs(None, view._renderSourcePackage(template))