Beispiel #1
0
    def setUp(self):
        self.app = Holocron(
            conf={
                'site': {
                    'title': 'MyTestSite',
                    'author': 'Tester',
                    'url': 'http://www.mytest.com/',
                },
                'encoding': {
                    'output': 'my-enc',
                },
                'paths': {
                    'output': 'path/to/output',
                },
                'ext': {
                    'enabled': [],
                    'feed': {
                        'save_as': 'myfeed.xml',
                        'posts_number': 3,
                    },
                },
            })
        self.feed = Feed(self.app)

        self.date_early = datetime(2012, 2, 2)
        self.date_moderate = datetime(2013, 4, 1)
        self.date_late = datetime(2014, 6, 12)

        self.date_early_updated = datetime(2012, 12, 6)
        self.date_moderate_updated = datetime(2013, 12, 6)
        self.date_late_updated = datetime(2014, 12, 6)

        self.post_early = mock.Mock(spec=Post,
                                    published=self.date_early,
                                    updated_local=self.date_early_updated,
                                    abs_url='http://www.post_early.com',
                                    title='MyEarlyPost')

        self.post_moderate = mock.Mock(
            spec=Post,
            published=self.date_moderate,
            updated_local=self.date_moderate_updated,
            abs_url='http://www.post_moderate.com')

        self.post_late = mock.Mock(spec=Post,
                                   published=self.date_late,
                                   updated_local=self.date_late_updated,
                                   url='www.post_late.com',
                                   abs_url='http://www.post_late.com',
                                   title='MyTestPost')

        self.late_id = '<id>http://www.post_late.com</id>'
        self.moderate_id = '<id>http://www.post_moderate.com</id>'
        self.early_id = '<id>http://www.post_early.com</id>'

        self.page = mock.Mock(spec=Page, url='www.page.com')
        self.static = mock.Mock(spec=Static, url='www.image.com')

        self.open_fn = 'holocron.ext.feed.open'
Beispiel #2
0
    def setUp(self):
        self.app = Holocron(conf={
            'sitename': 'MyTestSite',
            'siteurl': 'www.mytest.com',
            'author': 'Tester',

            'encoding': {
                'output': 'my-enc',
            },

            'paths': {
                'output': 'path/to/output',
            },

            'ext': {
                'enabled': [],
                'tags': {
                    'output': 'mypath/tags/{tag}',
                },
            },
        })
        self.tags = Tags(self.app)

        self.date_early = datetime(2012, 2, 2)
        self.date_moderate = datetime(2013, 4, 1)
        self.date_late = datetime(2014, 6, 12)

        self.post_early = mock.Mock(
            spec=Post,
            published=self.date_early,
            tags=['testtag1', 'testtag2'],
            title='MyTestPost',
            url='www.post_early.com')

        self.post_moderate = mock.Mock(
            spec=Post,
            published=self.date_moderate,
            url='www.post_moderate.com',
            tags=['testtag2', 'testtag3'])

        self.post_late = mock.Mock(
            spec=Post,
            published=self.date_late,
            url='www.post_late.com',
            tags=['testtag2'])

        self.post_malformed = mock.Mock(
            spec=Post,
            short_source='test',
            tags='testtag')

        self.page = mock.Mock(spec=Page)
        self.static = mock.Mock(spec=Static)

        self.open_fn = 'holocron.ext.tags.open'
Beispiel #3
0
    def setUp(self, getcwd, getctime, getmtime):
        """
        Prepares a document instance with a fake config.
        """
        filename = os.path.join(self._conf['paths.content'],
                                self.document_filename)

        self.app = Holocron(self._conf)
        self.app.add_converter(FakeConverter())

        self.doc = self.document_class(filename, self.app)
Beispiel #4
0
 def setUp(self):
     self.conv = ReStructuredText(
         Holocron(conf={
             'ext': {
                 'enabled': [],
             },
         }))
Beispiel #5
0
    def setUp(self):
        """
        Prepares a sitemap instance with a fake config.
        """
        self.app = Holocron(conf={
            'encoding': {
                'output': 'my-enc',
            },

            'paths': {
                'output': 'path/to/output',
            },

            'ext': {
                'enabled': [],
            },
        })
        self.sitemap = Sitemap(self.app)

        self.open_fn = 'holocron.ext.sitemap.open'

        self.post_url = 'http://example.com/post/'
        self.page_url = 'http://example.com/page/'

        self.page_date = datetime(2014, 12, 27)
        self.post_date = datetime(2013, 4, 1)

        self.post = mock.Mock(
            spec=Post, abs_url=self.post_url, updated_local=self.post_date)
        self.page = mock.Mock(
            spec=Page, abs_url=self.page_url, updated_local=self.page_date)
        self.static = mock.Mock(spec=Static)
Beispiel #6
0
    def test_extra_tables(self):
        """
        Converter has to support tables syntax.
        """
        self.conv = Markdown(Holocron(conf={
            'ext': {
                'enabled': [],
                'markdown': {
                    'extensions': ['markdown.extensions.extra'],
                },
            },
        }))

        _, html = self.conv.to_html(textwrap.dedent('''\
            column a | column b
            ---------|---------
               foo   |   bar
        '''))

        self.assertIn('table', html)

        self.assertIn('<th>column a</th>', html)
        self.assertIn('<th>column b</th>', html)

        self.assertIn('<td>foo</td>', html)
        self.assertIn('<td>bar</td>', html)
Beispiel #7
0
 def setUp(self):
     self.conv = Markdown(Holocron(conf={
         'ext': {
             'enabled': [],
             'markdown': {
                 'extensions': [],
             },
         },
     }))
Beispiel #8
0
class DocumentTestCase(HolocronTestCase):
    """
    A testcase helper that prepares a document instance.
    """

    _getcwd = 'cwd'  # fake current working dir, used by abspath
    _getctime = 662739000  # UTC: 1991/01/01 2:10pm
    _getmtime = 1420121400  # UTC: 2015/01/01 2:10pm

    _conf = Conf({
        'site': {
            'url': 'http://example.com',
        },
        'encoding': {
            'content': 'cont-enc',
            'output': 'out-enc',
        },
        'paths': {
            'content': './content',
            'output': './_output',
        }
    })

    document_class = None  # a document constructor
    document_filename = None  # a document filename, relative to the content

    @mock.patch('holocron.content.os.path.getmtime', return_value=_getmtime)
    @mock.patch('holocron.content.os.path.getctime', return_value=_getctime)
    @mock.patch('holocron.content.os.getcwd', return_value=_getcwd)
    def setUp(self, getcwd, getctime, getmtime):
        """
        Prepares a document instance with a fake config.
        """
        filename = os.path.join(self._conf['paths.content'],
                                self.document_filename)

        self.app = Holocron(self._conf)
        self.app.add_converter(FakeConverter())

        self.doc = self.document_class(filename, self.app)
Beispiel #9
0
    def test_user_settings(self):
        """
        Tests creating an instance with custom settings: check for settings
        overriding.
        """
        app = Holocron({
            'sitename': 'Luke Skywalker',
            'paths': {
                'content': 'path/to/content',
            },
        })

        conf = copy.deepcopy(app.default_conf)
        conf['sitename'] = 'Luke Skywalker'
        conf['paths']['content'] = 'path/to/content'

        self.assertEqual(app.conf, conf)
Beispiel #10
0
    def setUp(self):
        """
        Creates generator object and number of posts which will be further used
        in test sets.
        """
        self.app = Holocron(
            conf={
                'sitename': 'MyTestSite',
                'siteurl': 'www.mytest.com',
                'author': 'Tester',
                'encoding': {
                    'output': 'my-enc',
                },
                'paths': {
                    'output': 'path/to/output',
                },
                'ext': {
                    'enabled': [],
                },
            })
        self.index = Index(self.app)

        self.open_fn = 'holocron.ext.index.open'

        self.date_early = datetime(2012, 2, 2)
        self.date_late = datetime(2014, 6, 12)
        self.date_moderate = datetime(2013, 4, 1)

        self.post_early = mock.Mock(
            spec=Post,
            published=self.date_early,
            url='www.post_early.com',
        )

        self.post_moderate = mock.Mock(spec=Post,
                                       published=self.date_moderate,
                                       url='www.post_moderate.com')

        self.post_late = mock.Mock(spec=Post,
                                   published=self.date_late,
                                   url='www.post_late.com')

        self.page = mock.Mock(spec=Page)
        self.static = mock.Mock(spec=Static)
Beispiel #11
0
    def test_codehilite_extension(self):
        """
        Converter has to use Pygments to highlight code blocks.
        """
        self.conv = Markdown(Holocron(conf={
            'ext': {
                'enabled': [],
                'markdown': {
                    'extensions': ['markdown.extensions.codehilite'],
                },
            },
        }))

        _, html = self.conv.to_html(textwrap.dedent('''\
            test codeblock

                :::python
                lambda x: pass
        '''))

        self.assertRegexpMatches(html, '.*codehilite.*<pre>[\s\S]+</pre>.*')
Beispiel #12
0
    def test_code_macros_is_enabled(self):
        """
        Converter has to use Pygments to highlight code blocks.
        """
        self.conv = Creole(
            Holocron(
                conf={
                    'ext': {
                        'enabled': ['creole'],
                        'creole': {
                            'syntax_highlight': True,
                        }
                    }
                }))

        _, html = self.conv.to_html(
            textwrap.dedent('''\
            <<code ext=".py">>
                lambda x: pass
            <</code>>
        '''))

        self.assertRegexpMatches(html, '.*pygments.*<pre>[\s\S]+</pre>.*')
Beispiel #13
0
    def test_extra_code(self):
        """
        Converter has to support GitHub's fence code syntax.
        """
        self.conv = Markdown(Holocron(conf={
            'ext': {
                'enabled': [],
                'markdown': {
                    'extensions': [
                        'markdown.extensions.codehilite',
                        'markdown.extensions.extra',
                    ],
                },
            },
        }))

        _, html = self.conv.to_html(textwrap.dedent('''\
            ```python
            lambda x: pass
            ```
        '''))

        self.assertRegexpMatches(html, '.*codehilite.*<pre>[\s\S]+</pre>.*')
Beispiel #14
0
    def test_code_macros_is_disabled(self):
        """
        Converter shouldn't support code macros if syntax highlighting is
        turned off.
        """
        self.conv = Creole(
            Holocron(
                conf={
                    'ext': {
                        'enabled': ['creole'],
                        'creole': {
                            'syntax_highlight': False,
                        }
                    }
                }))

        _, html = self.conv.to_html(
            textwrap.dedent('''\
            <<code ext=".py">>
                lambda x: pass
            <</code>>
        '''))

        self.assertEqual(html, "[Error: Macro 'code' doesn't exist]")
Beispiel #15
0
 def setUp(self):
     self.conv = Creole(Holocron(conf={
         'ext': {
             'enabled': ['creole'],
         },
     }))
Beispiel #16
0
 def setUp(self):
     self.app = Holocron({
         'ext': {
             'enabled': [],
         },
     })
Beispiel #17
0
class TestHolocron(HolocronTestCase):
    def setUp(self):
        self.app = Holocron({
            'ext': {
                'enabled': [],
            },
        })

    def test_user_settings(self):
        """
        Tests creating an instance with custom settings: check for settings
        overriding.
        """
        app = Holocron({
            'sitename': 'Luke Skywalker',
            'paths': {
                'content': 'path/to/content',
            },
        })

        conf = copy.deepcopy(app.default_conf)
        conf['sitename'] = 'Luke Skywalker'
        conf['paths']['content'] = 'path/to/content'

        self.assertEqual(app.conf, conf)

    def test_add_converter(self):
        """
        Tests converter registration process.
        """
        class TestConverter(abc.Converter):
            extensions = ['.tst', '.test']

            def to_html(self, text):
                return {}, text

        # test registration process
        converter = TestConverter()
        self.assertEqual(len(self.app._converters), 0)
        self.app.add_converter(converter)
        self.assertEqual(len(self.app._converters), 2)

        self.assertIn('.tst', self.app._converters)
        self.assertIn('.test', self.app._converters)

        self.assertIs(self.app._converters['.tst'], converter)
        self.assertIs(self.app._converters['.test'], converter)

        # test protection from double registration
        self.app.add_converter(TestConverter())
        self.assertIs(self.app._converters['.tst'], converter)
        self.assertIs(self.app._converters['.test'], converter)

        # test force registration
        new_converter = TestConverter()
        self.app.add_converter(new_converter, _force=True)
        self.assertIs(self.app._converters['.tst'], new_converter)
        self.assertIs(self.app._converters['.test'], new_converter)

    def test_add_generator(self):
        """
        Tests generator registration process.
        """
        class TestGenerator(abc.Generator):
            def generate(self, text):
                pass

        # test registration process
        generator = TestGenerator()
        self.assertEqual(len(self.app._generators), 0)
        self.app.add_generator(generator)
        self.assertEqual(len(self.app._generators), 1)
        self.assertIn(generator, self.app._generators)

        # test double registration is allowed
        new_generator = TestGenerator()
        self.app.add_generator(new_generator)
        self.assertEqual(len(self.app._generators), 2)
        self.assertIn(new_generator, self.app._generators)

    @mock.patch('holocron.app.mkdir')
    @mock.patch('holocron.app.iterfiles')
    def test_run(self, iterfiles, mkdir):
        """
        Tests build process.
        """
        iterfiles.return_value = ['doc_a', 'doc_b', 'doc_c']
        self.app.__class__.document_factory = mock.Mock()
        self.app._copy_theme = mock.Mock()
        self.app._generators = {
            mock.Mock(): mock.Mock(),
            mock.Mock(): mock.Mock(),
        }

        self.app.run()

        # check iterfiles call signature
        iterfiles.assert_called_with(self.app.conf['paths']['content'],
                                     '[!_.]*', True)

        # check mkdir create ourpur dir
        mkdir.assert_called_with(self.app.conf['paths.output'])

        # check that generators was used
        for generator in self.app._generators:
            self.assertEqual(generator.generate.call_count, 1)

        self.app.__class__.document_factory.assert_has_calls([
            # check that document class was used to generate class instances
            mock.call('doc_a', self.app),
            mock.call('doc_b', self.app),
            mock.call('doc_c', self.app),
            # check that document instances were built
            mock.call().build(),
            mock.call().build(),
            mock.call().build(),
        ])
        self.assertEqual(self.app.__class__.document_factory.call_count, 3)

        # check that _copy_theme was called
        self.app._copy_theme.assert_called_once_with()

    @mock.patch('holocron.app.copy_tree')
    def test_copy_base_theme(self, mcopytree):
        """
        Tests that Holocron do copy default theme.
        """
        output = os.path.join(self.app.conf['paths.output'], 'static')
        theme = os.path.join(os.path.dirname(holocron.__file__), 'theme',
                             'static')

        self.app._copy_theme()

        mcopytree.assert_called_with(theme, output)

    @mock.patch('holocron.app.os.path.exists', return_value=True)
    @mock.patch('holocron.app.copy_tree')
    def test_copy_user_themes(self, mcopytree, _):
        """
        Tests that Holocron do copy user theme.
        """
        output = os.path.join(self.app.conf['paths.output'], 'static')
        theme = os.path.join(os.path.dirname(holocron.__file__), 'theme',
                             'static')

        self.app.add_theme('theme1')
        self.app.add_theme('theme2')
        self.app._copy_theme()

        self.assertEqual(mcopytree.call_args_list, [
            mock.call(theme, output),
            mock.call(os.path.join('theme1', 'static'), output),
            mock.call(os.path.join('theme2', 'static'), output),
        ])

    @mock.patch('holocron.app.os.path.exists', side_effect=[True, False])
    @mock.patch('holocron.app.copy_tree')
    def test_copy_user_themes_not_exist(self, mcopytree, _):
        """
        Tests that Holocron doesn't copy static if it's not exist.
        """
        output = os.path.join(self.app.conf['paths.output'], 'static')
        theme = os.path.join(os.path.dirname(holocron.__file__), 'theme',
                             'static')

        self.app.add_theme('theme1')
        self.app._copy_theme()

        self.assertEqual(mcopytree.call_args_list, [
            mock.call(theme, output),
        ])
Beispiel #18
0
 def setUp(self):
     self.app = Holocron()
 def setUp(self):
     self.holocron = Holocron(conf={
         'ext': {
             'enabled': ['creole'],
         },
     })
Beispiel #20
0
class TestTagsGenerator(HolocronTestCase):
    """
    Test tags generator.
    """
    #: generate html headers with years that is used for grouping in templates
    h_year = '<span class="year">{0}</span>'.format

    def setUp(self):
        self.app = Holocron(conf={
            'sitename': 'MyTestSite',
            'siteurl': 'www.mytest.com',
            'author': 'Tester',

            'encoding': {
                'output': 'my-enc',
            },

            'paths': {
                'output': 'path/to/output',
            },

            'ext': {
                'enabled': [],
                'tags': {
                    'output': 'mypath/tags/{tag}',
                },
            },
        })
        self.tags = Tags(self.app)

        self.date_early = datetime(2012, 2, 2)
        self.date_moderate = datetime(2013, 4, 1)
        self.date_late = datetime(2014, 6, 12)

        self.post_early = mock.Mock(
            spec=Post,
            published=self.date_early,
            tags=['testtag1', 'testtag2'],
            title='MyTestPost',
            url='www.post_early.com')

        self.post_moderate = mock.Mock(
            spec=Post,
            published=self.date_moderate,
            url='www.post_moderate.com',
            tags=['testtag2', 'testtag3'])

        self.post_late = mock.Mock(
            spec=Post,
            published=self.date_late,
            url='www.post_late.com',
            tags=['testtag2'])

        self.post_malformed = mock.Mock(
            spec=Post,
            short_source='test',
            tags='testtag')

        self.page = mock.Mock(spec=Page)
        self.static = mock.Mock(spec=Static)

        self.open_fn = 'holocron.ext.tags.open'

    @mock.patch('holocron.ext.tags.mkdir', mock.Mock())
    def _get_content(self, documents):
        """
        This helper method mocks the open function and return the content
        passed as input to write function.
        """
        with mock.patch(self.open_fn, mock.mock_open(), create=True) as mopen:
            self.tags.generate(documents)

            # extract what was generated and what was passed to f.write()
            content, = mopen().write.call_args[0]
            return content

    @mock.patch('holocron.ext.tags.mkdir')
    def _get_tags_content(self, documents, mock_mkdir):
        with mock.patch(self.open_fn, mock.mock_open(), create=True) as mopen:
            self.tags.generate(documents)

            open_calls = [c[0][0] for c in mopen.call_args_list if c != '']
            write_calls = [c[0][0] for c in mopen().write.call_args_list]
            mkdir_calls = [c[0][0] for c in mock_mkdir.call_args_list]

            # form a tuple that contains corresponding open and write calls
            # and sort it aplhabetically, so the testtag1 comes first
            content = list(zip(open_calls, write_calls, mkdir_calls))
            content.sort()

            return content

    def test_tag_template_building(self):
        """
        Test that tags function writes a post to a tag template.
        """
        posts = [self.post_early, self.post_moderate]

        content = self._get_tags_content(posts)

        # check that open, write and mkdir functions were called three times
        # according to the number of different tags in test documents
        for entry in content:
            self.assertEqual(len(entry), 3)

        # test that output html contains early_post with its unique tag
        self.assertEqual(
            'path/to/output/mypath/tags/testtag1/index.html', content[0][0])

        self.assertIn(self.h_year(2012), content[0][1])
        self.assertIn('<a href="www.post_early.com">', content[0][1])

        self.assertEqual('path/to/output/mypath/tags/testtag1', content[0][2])

        # test that output html contains posts with common tag
        self.assertEqual(
            'path/to/output/mypath/tags/testtag2/index.html', content[1][0])

        self.assertIn(self.h_year(2012), content[1][1])
        self.assertIn(self.h_year(2013), content[1][1])

        self.assertIn('<a href="www.post_early.com">', content[1][1])
        self.assertIn('<a href="www.post_moderate.com">', content[1][1])

        self.assertEqual('path/to/output/mypath/tags/testtag2', content[1][2])

        # test that output html contains moderate_post with its unique tag
        self.assertEqual(
            'path/to/output/mypath/tags/testtag3/index.html', content[2][0])

        self.assertIn(self.h_year(2013), content[2][1])
        self.assertIn('<a href="www.post_moderate.com">', content[2][1])

        self.assertEqual('path/to/output/mypath/tags/testtag3', content[2][2])

    def test_sorting_out_mixed_documents(self):
        """
        Test that Tags sorts out post documents from documents of other types.
        """
        documents = [self.page, self.static, self.post_late]
        content = self._get_content(documents)

        self.assertIn(self.h_year(2014), content)
        self.assertIn('<a href="www.post_late.com">', content)

    def test_posts_patching_with_tag_objects(self):
        """
        Test that Tags patches post's tags attribute.
        """
        posts = [self.post_late]

        self._get_tags_content(posts)

        self.assertEquals(self.post_late.tags[0].name, 'testtag2')
        self.assertEquals(self.post_late.tags[0].url, '/mypath/tags/testtag2/')

    @mock.patch('holocron.content.os.mkdir', mock.Mock())
    @mock.patch('holocron.content.os.path.getmtime')
    @mock.patch('holocron.content.os.path.getctime')
    def test_tags_are_shown_in_post(self, _, __):
        """
        Test that tags are actually get to the output.
        """
        # since we're interested in rendered page, let's register
        # a fake converter for that purpose
        self.app.add_converter(FakeConverter())

        data = textwrap.dedent('''\
            ---
            tags: [tag1, tag2]
            ---

            some text''')

        open_fn = 'holocron.content.open'
        with mock.patch(open_fn, mock.mock_open(read_data=data), create=True):
            post = Post('2015/05/23/filename.fake', self.app)

        self._get_content([post])

        with mock.patch(open_fn, mock.mock_open(), create=True) as mopen:
            post.build()
            content = mopen().write.call_args[0][0]

        err = 'Could not find link for #tag1.'
        self.assertIn('<a href="/mypath/tags/tag1/">#tag1</a>', content, err)

        err = 'Could not find link for #tag2.'
        self.assertIn('<a href="/mypath/tags/tag2/">#tag2</a>', content, err)

    @mock.patch('holocron.ext.tags.mkdir')
    def test_malformed_tags_are_skipped(self, mock_mkdir):
        """
        Test if tags formatting is correct.
        """
        content = self._get_tags_content([self.post_malformed])

        self.assertEqual(content, [])
Beispiel #21
0
 def _create_document(self, filename, getcwd, getctime, getmtime, _):
     app = Holocron({'paths': {
         'content': './content',
     }})
     app.add_converter(FakeConverter())
     return content.create_document(filename, app)
Beispiel #22
0
class TestFeedGenerator(HolocronTestCase):
    """
    Test feed generator.
    """
    def setUp(self):
        self.app = Holocron(
            conf={
                'site': {
                    'title': 'MyTestSite',
                    'author': 'Tester',
                    'url': 'http://www.mytest.com/',
                },
                'encoding': {
                    'output': 'my-enc',
                },
                'paths': {
                    'output': 'path/to/output',
                },
                'ext': {
                    'enabled': [],
                    'feed': {
                        'save_as': 'myfeed.xml',
                        'posts_number': 3,
                    },
                },
            })
        self.feed = Feed(self.app)

        self.date_early = datetime(2012, 2, 2)
        self.date_moderate = datetime(2013, 4, 1)
        self.date_late = datetime(2014, 6, 12)

        self.date_early_updated = datetime(2012, 12, 6)
        self.date_moderate_updated = datetime(2013, 12, 6)
        self.date_late_updated = datetime(2014, 12, 6)

        self.post_early = mock.Mock(spec=Post,
                                    published=self.date_early,
                                    updated_local=self.date_early_updated,
                                    abs_url='http://www.post_early.com',
                                    title='MyEarlyPost')

        self.post_moderate = mock.Mock(
            spec=Post,
            published=self.date_moderate,
            updated_local=self.date_moderate_updated,
            abs_url='http://www.post_moderate.com')

        self.post_late = mock.Mock(spec=Post,
                                   published=self.date_late,
                                   updated_local=self.date_late_updated,
                                   url='www.post_late.com',
                                   abs_url='http://www.post_late.com',
                                   title='MyTestPost')

        self.late_id = '<id>http://www.post_late.com</id>'
        self.moderate_id = '<id>http://www.post_moderate.com</id>'
        self.early_id = '<id>http://www.post_early.com</id>'

        self.page = mock.Mock(spec=Page, url='www.page.com')
        self.static = mock.Mock(spec=Static, url='www.image.com')

        self.open_fn = 'holocron.ext.feed.open'

    @mock.patch('holocron.ext.feed.mkdir', mock.Mock())
    def _get_content(self, documents):
        """
        This helper method mocks the open function and returns the content
        passed as input to write function.
        """
        with mock.patch(self.open_fn, mock.mock_open(), create=True) as mopen:
            self.feed.generate(documents)

            content, = mopen().write.call_args[0]
            return content

    def _xml_to_dict(self, xml):
        """
        Generates and returns python dict from an xml string passed as input.
        """
        parsed = minidom.parseString(xml)
        root = parsed.documentElement

        #: use this to sort DOM Elements from DOM Text containing \n and spaces
        def is_element(n):
            return n.nodeType == n.ELEMENT_NODE

        #: use this to parse <link> which contain attributes instead of values
        def is_attribute(n):
            return len(n.attributes) != 0

        #: use this to distinguish feed common elements (link, title) from post
        def has_child(n):
            return len(n.childNodes) < 2

        urls = [url for url in filter(is_element, root.childNodes)]

        entries = {}
        url_data = {}
        #: use to store dictionnaries with post data
        posts = []

        #: links in feed differ by attribute (rel or alt), use this attribute
        #: as a key to avoid dicts with same key
        key = '{link}.{fmt}'.format

        for url in urls:
            if has_child(url):
                if is_attribute(url):
                    link = key(link=url.nodeName, fmt=url.getAttribute('rel'))
                    entries[link] = url.getAttribute('href')
                else:
                    entries[url.nodeName] = url.firstChild.nodeValue
            else:
                for attr in filter(is_element, url.childNodes):
                    if is_attribute(attr):
                        url_data[attr.nodeName] = attr.getAttribute('href')
                    else:
                        url_data[attr.nodeName] = attr.firstChild.nodeValue

                posts.append(url_data)
                entries[url.nodeName] = posts

        content = {root.nodeName: entries}

        return content

    @mock.patch('holocron.ext.feed.mkdir', mock.Mock())
    def test_feed_filename_and_enc(self):
        """
        Feed function has to save feed xml file to a proper location and with
        proper filename. All settings are fetched from the configuration file.
        """
        with mock.patch(self.open_fn, mock.mock_open(), create=True) as mopen:
            self.feed.generate([])

            self.assertEqual(mopen.call_args[0][0],
                             'path/to/output/myfeed.xml')
            self.assertEqual(mopen.call_args[1]['encoding'], 'my-enc')

    def test_feed_encoding_attr(self):
        """
        The feed.xml has to have an XML tag with right encoding.
        """
        output = self._get_content([])

        self.assertIn('encoding="my-enc"', output)

    def test_feed_template(self):
        """
        Test that feed writes correct values to an xml template.
        """
        content = self._get_content([])
        content = self._xml_to_dict(content)

        feed = content['feed']

        self.assertEqual('MyTestSite', feed['title'])
        self.assertEqual('http://www.mytest.com/', feed['id'])
        self.assertEqual('http://www.mytest.com/myfeed.xml', feed['link.self'])
        self.assertEqual('http://www.mytest.com/', feed['link.alternate'])

    def test_feed_empty(self):
        """
        Feed runned on an empty list of documents has to create an xml file, no
        posts should be listed there.
        """
        content = self._get_content([])
        content = self._xml_to_dict(content)

        self.assertNotIn('entry', content['feed'])

    def test_feed_with_posts(self):
        """
        Feed function has to f*****g work.
        """
        # here we require only one post to test its content correctness
        # we test posts in other test suites
        self.feed._conf['posts_number'] = 1

        content = self._get_content([self.post_early, self.post_late])
        content = self._xml_to_dict(content)

        self.assertIn('entry', content['feed'])
        self.assertEqual(len(content['feed']['entry']), 1)

        feed = content['feed']['entry'][0]

        self.assertEqual('http://www.post_late.com', feed['link'])
        self.assertEqual(self.date_late_updated.isoformat(), feed['updated'])

        self.assertEqual(self.date_late.isoformat(), feed['published'])

        self.assertEqual('http://www.post_late.com', feed['id'])
        self.assertEqual('MyTestPost', feed['title'])

    def test_posts_in_front_order(self):
        """
        Tests posts ordering. Feed must display older posts first.
        """
        posts = [self.post_early, self.post_moderate, self.post_late]
        content = self._get_content(posts)

        #: test that the latest post comes first
        self.assertIn(self.late_id, content)

        #: delete information about the latest post
        post_position = content.index(self.late_id) + len(self.late_id)
        content = content[post_position:]

        self.assertIn(self.moderate_id, content)

        #: another strim to delete next post
        post_position = content.index(self.moderate_id) + len(self.moderate_id)
        content = content[post_position:]

        self.assertIn(self.early_id, content)

    def test_posts_in_reverse_order(self):
        """
        Tests posts ordering. Feed must display older posts first.
        """
        posts = [self.post_late, self.post_moderate, self.post_early]
        content = self._get_content(posts)

        #: test that the latest post comes first
        self.assertIn(self.late_id, content)

        #: delete information about the latest post
        post_position = content.index(self.late_id) + len(self.late_id)
        content = content[post_position:]

        self.assertIn(self.moderate_id, content)

        #: another strim to delete next post
        post_position = content.index(self.moderate_id) + len(self.moderate_id)
        content = content[post_position:]

        self.assertIn(self.early_id, content)

    def test_mixed_documents(self):
        """
        Test that feed generator sorts out post documents out of other types.
        """
        documents = [self.page, self.post_late, self.static]
        content = self._get_content(documents)

        self.assertNotIn('www.page.com', content)
        self.assertNotIn('www.image.com', content)
        self.assertIn('www.post_late.com', content)

    @mock.patch('holocron.content.os.mkdir', mock.Mock())
    @mock.patch('holocron.content.os.path.getmtime')
    @mock.patch('holocron.content.os.path.getctime')
    def test_feed_link_in_html_header(self, _, __):
        """
        Test that html pages have the link to feed.
        """
        # since we're interested in rendered page, let's register
        # a fake converter for that purpose
        self.app.add_converter(FakeConverter())

        open_fn = 'holocron.content.open'
        with mock.patch(open_fn, mock.mock_open(read_data=''), create=True):
            page = Page('filename.fake', self.app)

        with mock.patch(open_fn, mock.mock_open(), create=True) as mopen:
            page.build()
            content = mopen().write.call_args[0][0]

        err = 'could not find link to feed in html header'
        self.assertIn(
            '<link rel="alternate" type="application/atom+xml" '
            'href="http://www.mytest.com/myfeed.xml" title="MyTestSite">',
            content, err)