diff --git a/ckanext/example_theme/tests/test_example_theme.py b/ckanext/example_theme/tests/test_example_theme.py
new file mode 100644
index 00000000000..00490d5f2c8
--- /dev/null
+++ b/ckanext/example_theme/tests/test_example_theme.py
@@ -0,0 +1,471 @@
+'''Functional tests for the plugins in ckanext.example_theme.
+
+These tests are pretty thin. They exist just so that if a change in CKAN
+completely breaks one of the theming examples from the docs, hopefully one of
+these tests will be failing.
+
+'''
+import webtest
+import pylons.config as config
+import bs4
+
+import ckan.config.middleware
+import ckan.plugins as plugins
+import ckan.plugins.toolkit as toolkit
+import ckan.new_tests.factories as factories
+import ckan.new_tests.helpers as helpers
+
+
+def get_test_app(plugin):
+
+ # Disable the legacy templates feature.
+ config['ckan.legacy_templates'] = False
+ wsgiapp = ckan.config.middleware.make_app(config['global_conf'],
+ **config)
+ app = webtest.TestApp(wsgiapp)
+
+ plugins.load(plugin)
+
+ return app
+
+
+class TestExampleEmptyPlugin(object):
+
+ @classmethod
+ def setup_class(cls):
+ cls.app = get_test_app('example_theme_v01_empty_extension')
+
+ @classmethod
+ def teardown_class(cls):
+ plugins.unload('example_theme_v01_empty_extension')
+
+ def test_front_page_loads_okay(self):
+
+ # The v01_empty_extension plugin doesn't do anything, so we just test
+ # that the front page loads without crashing OK (i.e. CKAN has found
+ # and loaded the plugin successfully).
+ offset = toolkit.url_for(controller='home', action='index')
+ result = self.app.get(offset)
+ F
+ assert result.status == '200 OK'
+
+ def test_that_plugin_is_loaded(self):
+ plugins.plugin_loaded('example_theme_v01_empty_extension')
+
+
+class TestExampleEmptyTemplatePlugin(object):
+
+ @classmethod
+ def setup_class(cls):
+ cls.app = get_test_app('example_theme_v02_empty_template')
+
+ @classmethod
+ def teardown_class(cls):
+ plugins.unload('example_theme_v02_empty_template')
+
+ def test_front_page_is_empty(self):
+ offset = toolkit.url_for(controller='home', action='index')
+ result = self.app.get(offset)
+ assert result.body == '', 'The front page should be empty'
+
+
+class TestExampleJinjaPlugin(object):
+
+ @classmethod
+ def setup_class(cls):
+ cls.app = get_test_app('example_theme_v03_jinja')
+
+ @classmethod
+ def teardown_class(cls):
+ plugins.unload('example_theme_v03_jinja')
+
+ def test_site_title(self):
+ offset = toolkit.url_for(controller='home', action='index')
+ result = self.app.get(offset)
+ site_title = config.get('ckan.site_title')
+ assert ('The title of this site is: {site_title}'.format(
+ site_title=site_title) in result.body)
+
+ def test_plugins(self):
+ offset = toolkit.url_for(controller='home', action='index')
+ result = self.app.get(offset)
+ for plugin in toolkit.aslist(config.get('ckan.plugins')):
+ assert plugin in result.body
+
+ def test_page_view_tracking_enabled(self):
+ config['ckan.tracking_enabled'] = True
+ offset = toolkit.url_for(controller='home', action='index')
+ result = self.app.get(offset)
+ assert toolkit.asbool(config.get('ckan.tracking_enabled')) is True
+ assert ("CKAN's page-view tracking feature is enabled." in
+ result.body)
+
+ def test_comment(self):
+ offset = toolkit.url_for(controller='home', action='index')
+ result = self.app.get(offset)
+ assert ('This text will not appear in the output when this template '
+ 'is rendered' not in result.body)
+
+
+class TestExampleCKANExtendsPlugin(object):
+
+ @classmethod
+ def setup_class(cls):
+ cls.app = get_test_app('example_theme_v04_ckan_extends')
+
+ @classmethod
+ def teardown_class(cls):
+ plugins.unload('example_theme_v04_ckan_extends')
+
+ def test_front_page(self):
+
+ offset = toolkit.url_for(controller='home', action='index')
+ response = self.app.get(offset)
+ soup = response.html
+
+ # Just check for some random text from the default front page,
+ # to test that {% ckan_extends %} worked.
+ assert ("This is a nice introductory paragraph about CKAN or the site "
+ "in general. We don't have any copy to go here yet but soon "
+ "we will" in [s.strip() for s in soup.strings])
+
+ # TODO: It would be better if we also tested that the custom template
+ # was the template that was rendered, and it didn't just render the
+ # default front page template directly.
+
+
+class TestExampleBlockPlugin(object):
+
+ @classmethod
+ def setup_class(cls):
+ cls.app = get_test_app('example_theme_v05_block')
+
+ @classmethod
+ def teardown_class(cls):
+ plugins.unload('example_theme_v05_block')
+
+ def test_front_page(self):
+ offset = toolkit.url_for(controller='home', action='index')
+ result = self.app.get(offset)
+ assert 'Hello block world!' in result.body
+
+
+class TestExampleSuperPlugin(object):
+
+ @classmethod
+ def setup_class(cls):
+ cls.app = get_test_app('example_theme_v06_super')
+
+ @classmethod
+ def teardown_class(cls):
+ plugins.unload('example_theme_v06_super')
+
+ def test_front_page(self):
+ offset = toolkit.url_for(controller='home', action='index')
+ response = self.app.get(offset)
+
+ # We're going to parse the response using beautifulsoup.
+ soup = response.html
+
+ # Get the 'This paragraph will be added to the top' paragraph.
+ matches = [p for p in soup.find_all('p')
+ if p.get_text(' ', strip=True) == 'This paragraph will be '
+ 'added to the top of the featured_group block.']
+ assert len(matches) == 1
+ top = matches[0]
+
+ # Get the 'This paragraph will be added to the bottom' paragraph.
+ matches = [p for p in soup.find_all('p')
+ if p.get_text(' ', strip=True) == 'This paragraph will be '
+ 'added to the bottom of the featured_group block.']
+ assert len(matches) == 1
+ bottom = matches[0]
+
+ # Find the featured_groups div.
+ matches = soup.select('#featured_groups')
+ assert len(matches) == 1
+ div = matches[0]
+
+ # Assert that the three elements appear in the order they should.
+ assert div in top.next_elements
+ assert bottom in div.next_elements
+
+
+class TestExampleHelperFunctionPlugin(object):
+
+ @classmethod
+ def setup_class(cls):
+ cls.app = get_test_app('example_theme_v07_helper_function')
+
+ @classmethod
+ def teardown_class(cls):
+ plugins.unload('example_theme_v07_helper_function')
+
+ def test_helper_function(self):
+
+ # Make a user and a dataset, so we have some activities in our
+ # activity stream.
+ user = factories.User()
+ dataset = factories.Dataset(user=user)
+
+ offset = toolkit.url_for(controller='home', action='index')
+ response = self.app.get(offset)
+ soup = response.html
+
+ # Test that the activity stream is being rendered, for testing for
+ # some text we know should be on the page.
+ assert [e for e in soup.find_all()
+ if e.get_text(' ', strip=True).startswith(
+ '{user} created the dataset {dataset}'.format(
+ user=user['fullname'], dataset=dataset['title']))]
+
+
+class TestExampleCustomHelperFunctionPlugin(object):
+
+ @classmethod
+ def setup_class(cls):
+ cls.app = get_test_app('example_theme_v08_custom_helper_function')
+
+ @classmethod
+ def teardown_class(cls):
+ plugins.unload('example_theme_v08_custom_helper_function')
+
+ def test_most_popular_groups(self):
+
+ # Create three groups with 3, 2 and 1 datasets each.
+ user = factories.User()
+ most_popular_group = factories.Group(user=user)
+ # FIXME: Add the datasets to the groups!
+ dataset_one = factories.Dataset(user=user)
+ dataset_two = factories.Dataset(user=user)
+ dataset_three = factories.Dataset(user=user)
+ second_most_popular_group = factories.Group(user=user)
+ dataset_four = factories.Dataset(user=user)
+ dataset_five = factories.Dataset(user=user)
+ third_most_popular_group = factories.Group(user=user)
+ dataset_six = factories.Dataset(user=user)
+
+ offset = toolkit.url_for(controller='home', action='index')
+ response = self.app.get(offset)
+ soup = response.html
+
+ # Find the 'most popular groups' list.
+ matches = soup.select('ul#most-popular-groups')
+ assert len(matches) == 1
+ ul = matches[0]
+
+ # Assert that the three groups are listed in the right order.
+ list_items = ul.find_all('li')
+ assert len(list_items) == 3
+ assert list_items[0].get_text() == most_popular_group['title']
+ assert list_items[1].get_text() == second_most_popular_group['title']
+ assert list_items[2].get_text() == third_most_popular_group['title']
+
+
+class TestExampleSnippetPlugin(object):
+
+ @classmethod
+ def setup_class(cls):
+ cls.app = get_test_app('example_theme_v09_snippet')
+
+ @classmethod
+ def teardown_class(cls):
+ plugins.unload('example_theme_v09_snippet')
+
+ def test_snippet(self):
+
+ offset = toolkit.url_for(controller='home', action='index')
+ response = self.app.get(offset)
+ soup = response.html
+
+ # Just test that the snippet was used.
+ comments = soup.findAll(text=lambda text:isinstance(text, bs4.Comment))
+ assert 'Snippet group/snippets/group_list.html start' in (
+ comment.strip() for comment in comments)
+
+
+class TestExampleCustomSnippetPlugin(object):
+
+ @classmethod
+ def setup_class(cls):
+ cls.app = get_test_app('example_theme_v10_custom_snippet')
+
+ @classmethod
+ def teardown_class(cls):
+ plugins.unload('example_theme_v10_custom_snippet')
+
+ def test_most_popular_groups(self):
+
+ # Create three groups with 3, 2 and 1 datasets each.
+ user = factories.User()
+ most_popular_group = factories.Group(user=user)
+ # FIXME: Add the datasets to the groups!
+ factories.Dataset(user=user)
+ factories.Dataset(user=user)
+ factories.Dataset(user=user)
+ second_most_popular_group = factories.Group(user=user)
+ factories.Dataset(user=user)
+ factories.Dataset(user=user)
+ third_most_popular_group = factories.Group(user=user)
+ factories.Dataset(user=user)
+
+ offset = toolkit.url_for(controller='home', action='index')
+ response = self.app.get(offset)
+ soup = response.html
+
+ # Find the 'most popular groups' list and check that it has the right
+ # number of groups in the right order.
+ matches = [h for h in soup.find_all('h3')
+ if h.text == 'Most popular groups']
+ assert len(matches) == 1
+ h = matches[0]
+ ul = h.find_next_sibling()
+ list_items = ul.find_all('li')
+ assert len(list_items) == 3
+ assert (list_items[0].find_all('h3')[0].text
+ == most_popular_group['title'])
+ assert (list_items[1].find_all('h3')[0].text
+ == second_most_popular_group['title'])
+ assert (list_items[2].find_all('h3')[0].text
+ == third_most_popular_group['title'])
+
+
+class TestExampleHTMLAndCSSPlugin(object):
+
+ @classmethod
+ def setup_class(cls):
+ cls.app = get_test_app('example_theme_v11_HTML_and_CSS')
+
+ @classmethod
+ def teardown_class(cls):
+ plugins.unload('example_theme_v11_HTML_and_CSS')
+
+ def test_most_popular_groups(self):
+
+ # Create three groups with 3, 2 and 1 datasets each.
+ user = factories.User()
+ most_popular_group = factories.Group(user=user)
+ # FIXME: Add the datasets to the groups!
+ factories.Dataset(user=user)
+ factories.Dataset(user=user)
+ factories.Dataset(user=user)
+ second_most_popular_group = factories.Group(user=user)
+ factories.Dataset(user=user)
+ factories.Dataset(user=user)
+ third_most_popular_group = factories.Group(user=user)
+ factories.Dataset(user=user)
+
+ offset = toolkit.url_for(controller='home', action='index')
+ response = self.app.get(offset)
+ soup = response.html
+
+ # Find the 'most popular groups' list and check that it has the right
+ # number of groups in the right order.
+ matches = [h for h in soup.find_all('h3')
+ if h.text == 'Most popular groups']
+ assert len(matches) == 1
+ h = matches[0]
+ ul = h.find_next('ul')
+ list_items = ul.find_all('li')
+ assert len(list_items) == 3
+ assert (list_items[0].find_all('h3')[0].text
+ == most_popular_group['title'])
+ assert (list_items[1].find_all('h3')[0].text
+ == second_most_popular_group['title'])
+ assert (list_items[2].find_all('h3')[0].text
+ == third_most_popular_group['title'])
+
+
+class TestExampleCustomCSSPlugin(object):
+
+ @classmethod
+ def setup_class(cls):
+ cls.app = get_test_app('example_theme_v13_custom_css')
+
+ @classmethod
+ def teardown_class(cls):
+ plugins.unload('example_theme_v13_custom_css')
+
+ def test_custom_css(self):
+
+ offset = toolkit.url_for(controller='home', action='index')
+ response = self.app.get(offset)
+ soup = response.html
+
+ matches = soup.find_all('link', rel='stylesheet',
+ href='/example_theme.css')
+ assert len(matches) == 1
+ link = matches[0]
+ url = link['href']
+ # FIXME: It looks like static files aren't working in tests?
+ response = self.app.get(url)
+ assert response.status == '200 OK'
+
+
+class TestExampleMoreCustomCSSPlugin(object):
+
+ @classmethod
+ def setup_class(cls):
+
+ # Disable the legacy templates feature.
+ config['ckan.legacy_templates'] = False
+ wsgiapp = ckan.config.middleware.make_app(config['global_conf'],
+ **config)
+ cls.app = webtest.TestApp(wsgiapp)
+
+ plugins.load('example_theme_v14_more_custom_css')
+
+ @classmethod
+ def teardown_class(cls):
+ plugins.unload('example_theme_v14_more_custom_css')
+
+ def test_custom_css(self):
+
+ offset = toolkit.url_for(controller='home', action='index')
+ response = self.app.get(offset)
+ soup = response.html
+
+ matches = soup.find_all('link', rel='stylesheet',
+ href='/example_theme.css')
+ assert len(matches) == 1
+ link = matches[0]
+ url = link['href']
+ # FIXME: It looks like static files aren't working in tests?
+ response = self.app.get(url)
+ assert response.status == '200 OK'
+
+
+class TestExampleFanstaticPlugin(object):
+
+ @classmethod
+ def setup_class(cls):
+
+ # Disable the legacy templates feature.
+ config['ckan.legacy_templates'] = False
+ wsgiapp = ckan.config.middleware.make_app(config['global_conf'],
+ **config)
+ cls.app = webtest.TestApp(wsgiapp)
+
+ plugins.load('example_theme_v15_fanstatic')
+
+ @classmethod
+ def teardown_class(cls):
+ plugins.unload('example_theme_v15_fanstatic')
+
+ def test_fanstatic(self):
+
+ offset = toolkit.url_for(controller='home', action='index')
+ response = self.app.get(offset)
+ soup = response.html
+
+ # Test that Fanstatic has inserted one tag for the
+ # example_theme.css file.
+ matches = [link for link in soup.find_all('link', rel='stylesheet')
+ if 'example_theme.css' in link['href']]
+ assert len(matches) == 1
+ link = matches[0]
+
+ # Test that there is something at the tag's URL.
+ url = link['href']
+ response = self.app.get(url)
+ assert response.status == '200 OK'