# HG changeset patch
# User gerg.ward+moin@gmail.com
# Date 1225379530 14400
# Node ID abcab597f0bf4bf291c8df9903dc09d7e92d8349
# Parent  70a5344a1c5645cb9b02646c2d7a31f3773a5084
Add tests for Include macro.

diff -r 70a5344a1c56 -r abcab597f0bf MoinMoin/macro/_tests/test_Include.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/macro/_tests/test_Include.py	Thu Oct 30 11:12:10 2008 -0400
@@ -0,0 +1,437 @@
+# -*- coding: utf-8 -*-
+"""
+    MoinMoin - MoinMoin.macro.Include Tests
+
+    @copyright: 2008 MoinMoin:GregWard
+    @license: GNU GPL, see COPYING for details.
+"""
+
+import re
+from MoinMoin._tests import become_trusted, create_page, make_macro, nuke_page
+
+class BaseTest:
+    includer_pagename = u'IncluderPage'
+
+    def _execute(self, args):
+        """Execute macro Include in self.includer_page with args, returning the result."""
+        macro = make_macro(self.request, self.includer_page)
+        result = macro.execute('Include', args)
+        print "macro result: %r" % result
+        return result
+
+    def _assert_regex(self, expect_re, actual_result, pos):
+        print "expected result to match %s from pos %d" % (expect_re.pattern, pos)
+        match = expect_re.search(actual_result, pos=pos)
+        assert match is not None
+        return match
+
+    def _assert_included(self, expect_content, actual_result, pos=0):
+        # Hmmm: the space after the included content seems strange, but
+        # it is how Include behaves.
+        content_re = re.compile(r'<p class="\w+">%s<span' % re.escape(expect_content + " "))
+        return self._assert_regex(content_re, actual_result, pos=pos)
+
+    def _assert_sysmsg(self, expect_class, expect_message, actual_result, pos=0):
+        msg_re = re.compile(r'<p><strong class="%s">%s</strong></p>'
+                            % (re.escape(expect_class), re.escape(expect_message)))
+        return self._assert_regex(msg_re, actual_result, pos=pos)
+
+    _open_heading_pattern  = r'<h%d( id=\"[\w\.\-]+\")?>'
+    _close_heading_pattern = r'</h%d>'
+
+    _open_link_pattern  = r'<a( class=\"[^\"]*\")? href=\"%s\">'
+    _close_link_pattern = r'</a>'
+
+    def _assert_heading(self, expect_level, expect_content, actual_result, pos=0):
+        heading_re = re.compile((self._open_heading_pattern % expect_level) +
+                                re.escape(expect_content) +
+                                (self._close_heading_pattern % expect_level))
+        return self._assert_regex(heading_re, actual_result, pos=pos)
+
+    def _assert_heading_link(self, expect_level, expect_content, expect_href_pattern,
+                             actual_result, pos=0):
+        heading_re = re.compile((self._open_heading_pattern % expect_level) +
+                                (self._open_link_pattern % expect_href_pattern) +
+                                re.escape(expect_content) +
+                                (self._close_link_pattern) +
+                                (self._close_heading_pattern % expect_level))
+        return self._assert_regex(heading_re, actual_result, pos=pos)
+
+    def _assert_link(self, expect_href_pattern, expect_text, actual_result, pos=0):
+        link_re = re.compile(r'<a (class=\"[^\"]*\")?\s*href=\"%s\">%s</a>'
+                             % (expect_href_pattern, re.escape(expect_text)))
+        return self._assert_regex(link_re, actual_result, pos=pos)
+
+class TestInclude(BaseTest):
+    """tests for macro Include"""
+    included_pagename = u'IncludedPage'
+    included_content  = u'Here is included content!'
+
+    def setup_method(self, method):
+        assert isinstance(self, TestInclude)
+
+        request = self.request
+        become_trusted(request)
+        self._create_included_page()
+        self.includer_page = create_page(
+            request, self.includer_pagename, u'Argh')
+
+    def teardown_method(self, method):
+        nuke_page(self.request, self.included_pagename)
+        nuke_page(self.request, self.includer_pagename)
+        self.request.action = "show"    # in case of tinkering
+
+    def _create_included_page(self, pagename=included_pagename, content=included_content):
+        return create_page(self.request, pagename, content)
+
+    def testIncludeSimple(self):
+        result = self._execute(u'IncludedPage')
+        self._assert_included(self.included_content, result)
+
+    def testIncludeNoArgs(self):
+        # with no args, Include() expands to an error message
+        result = self._execute(u'')
+        assert 'Invalid include arguments' in result
+
+    def testIncludeBadPage(self):
+        # with a bad page name, Include returns the empty string (no error message!)
+        result = self._execute(u'BogusPage')
+        assert result == ""
+
+    def testIncludeRecursive(self):
+        # a page cannot include itself -- doing so returns an error message
+        self._create_included_page(content=u'Hello <<Include(IncludedPage)>> recursion!')
+        result = self._execute(u'IncludedPage')
+        self._assert_sysmsg('error', 'Recursive include of "IncludedPage" forbidden', result)
+
+    def testIncludeRecursiveIndirect(self):
+        # indirect recursion -- A includes B, B includes A -- is also detected
+        self._create_included_page(pagename=u'Included1',
+                                   content=u'Outer: <<Include(Included2)>> blah')
+        self._create_included_page(pagename=u'Included2',
+                                   content=u'Inner: <<Include(Included1)>> argh')
+        try:
+            result = self._execute(u'Included1')
+            self._assert_sysmsg('error', 'Recursive include of "Included1" forbidden', result)
+        finally:
+            nuke_page(self.request, u'Included1')
+            nuke_page(self.request, u'Included2')
+
+    def testIncludePermissions(self):
+        # including a page that the current user does not have rights to
+        # returns an empty result
+        permissions = self.request.user.may
+        saved = permissions.read
+        try:
+            # Blecch!  This is rather evil.  But it's easier than trying to figure
+            # out how to correctly revoke the current user's permission to read
+            # IncludedPage.  ;-)
+            permissions.read = lambda pagename: False
+            result = self._execute(u'IncludedPage')
+            assert result == ""
+        finally:
+            permissions.read = saved
+
+    def testIncludeMarkup(self):
+        # If the included page includes wiki markup, it is rendered to
+        # HTML by the macro.
+        # XXX this makes assumptions about the formatter: is that evil?
+        self._create_included_page(content=u'This is \'\'\'bold\'\'\' text')
+        result = self._execute(u'IncludedPage')
+        self._assert_included('This is <strong>bold</strong> text', result)
+
+    def testIncludeHeading(self):
+        # You can add a heading to the included content by passing 'heading'
+        # arg to Include.  The heading is actually a link to the included page,
+        # which we test by using _assert_heading_link().
+        heading = u'Flubby Flobby'
+        result = self._execute(u'IncludedPage, "%s"' % heading)
+
+        match = self._assert_heading_link(1, heading, r'\./IncludedPage', result)
+        match = self._assert_included(self.included_content, result, pos=match.end())
+
+    def testIncludeHeadingLevel(self):
+        # Add a heading with a specific level.
+        heading = u'Weeble Wobble'
+        result = self._execute(u'IncludedPage, "%s", 3' % heading)
+
+        match = self._assert_heading_link(3, heading, r'\./IncludedPage', result)
+        match = self._assert_included(self.included_content, result, pos=match.end())
+
+    def testIncludeFrom(self):
+        # Start including at the point right after text that matches the 'from' regex.
+        included_content = 'Here is some --delimited-- content'
+        self._create_included_page(content=included_content)
+        result = self._execute(u'IncludedPage,,, from="-+"')
+
+        self._assert_included('delimited-- content', result)
+        assert "Here is some" not in result
+
+    def testIncludeFromBadRegex(self):
+        # If the user passes a bad "from" regex -- say, r"foo[" -- then
+        # Include assumes the user meant to escape the metacharacters
+        # and does it for them, changing the regex to r"foo\[".
+        included_content = u'start including after foo[that string].'
+        self._create_included_page(content=included_content)
+
+        # First: do it with the correct regex syntax, so Include does
+        # not have to catch re.error.
+        result1 = self._execute(u'IncludedPage,,, from="foo\\["')
+        self._assert_included(u'that string].', result1)
+
+        # Now trip the exception and see that Include escapes the "[".
+        result2 = self._execute(u'IncludedPage,,, from="foo["')
+        self._assert_included(u'that string].', result2)
+
+    def testIncludeFromNoMatch(self):
+        # If the 'from' regex does not match the included page, Include
+        # inserts a warning message and then completely includes the
+        # other page.
+        result = self._execute(u'IncludedPage,,, from="foo"')
+        self._assert_sysmsg('warning', 'Include: Nothing found for "foo"!', result)
+        self._assert_included(self.included_content, result)
+
+    # Sigh.  The tests for 'to' are very similar to the tests for
+    # 'from'.  But the code being tested is also quite repetitive, so
+    # it's important to test both 'from' and 'to' thoroughly before
+    # refactoring!
+
+    def testIncludeTo(self):
+        # Stop including at the point before after text that matches the 'to' regex.
+        included_content = 'Here is some --delimited-- content'
+        self._create_included_page(content=included_content)
+        result = self._execute(u'IncludedPage,,, to="-+"')
+
+        self._assert_included('Here is some ', result)
+        assert "--delimited-- content" not in result
+
+    def testIncludeToBadRegex(self):
+        # As with 'from', a bad 'to' regex gets quoted and tried again.
+        included_content = u'include up to foo[that string'
+        self._create_included_page(content=included_content)
+
+        # First with correct regex syntax.
+        result1 = self._execute(u'IncludedPage,,, to="foo\\["')
+        self._assert_included(u'include up to ', result1)
+
+        # Now trip the exception and make sure we get the same thing.
+        result2 = self._execute(u'IncludedPage,,, to="foo["')
+        self._assert_included(u'include up to ', result2)
+
+    def testIncludeToNoMatch(self):
+        # As with 'from', a bad 'to' regex results in a warning.
+        result = self._execute(u'IncludedPage,,, to="foo"')
+        self._assert_sysmsg('warning', 'Include: Nothing found for "foo"!', result)
+        self._assert_included(self.included_content, result)
+
+    def testIncludeEditLinks(self):
+        result = self._execute(u'IncludedPage,,, editlink')
+        match = self._assert_included(self.included_content, result)
+        match = self._assert_link(
+            ur'\./IncludedPage', u'[IncludedPage]', result, pos=match.end())
+        match = self._assert_link(
+            ur'\./IncludedPage\?action=edit.*?', u'[edit]', result, pos=match.end())
+
+    def testIncludeHeadingDisablesEditLink(self):
+        # Weird: supplying a 'heading' for the included page disables the
+        # editlink.  Not sure if this is actually desirable behaviour, but these
+        # tests are initially to ensure that my refactoring does not change
+        # existing behaviour.
+        heading = u'included stuff:'
+        result = self._execute(u'IncludedPage, "%s",, editlink' % heading)
+
+        match = self._assert_heading_link(1, heading, r'\./IncludedPage', result)
+        match = self._assert_included(self.included_content, result, pos=match.end())
+        assert u'[IncludedPage]' not in result
+        assert u'[edit]' not in result
+
+        # Just for fun, repeat with an explicitly supplied 'level'.
+        result = self._execute(u'IncludedPage, "%s", 4, editlink' % heading)
+
+        match = self._assert_heading_link(4, heading, r'\./IncludedPage', result)
+        match = self._assert_included(self.included_content, result, pos=match.end())
+        assert u'[IncludedPage]' not in result
+        assert u'[edit]' not in result
+
+
+# Certain values of request.action imply "print mode", which changes the
+# behaviour of Include.
+class TestIncludePrintMode(BaseTest):
+
+    included_pagename = u'IncludedPage'
+    included_content = u'Yargghh!!'
+
+    def setup_class(cls):
+        become_trusted(cls.request)
+        cls.includer_page = create_page(cls.request, cls.includer_pagename, u'Foo!')
+        create_page(cls.request, cls.included_pagename, cls.included_content)
+
+        assert cls.request.action == "show"
+        cls.request.action = "print"
+
+    def teardown_class(cls):
+        cls.request.action = "show"
+        nuke_page(cls.request, cls.includer_pagename)
+        nuke_page(cls.request, cls.included_pagename)
+
+    def testIncludePrintModeSimple(self):
+        # A basic Include is unaffected by print mode: this test is basically a
+        # repeat of testIncludeSimple().
+        assert self.request.action == "print"
+        result = self._execute(u'IncludedPage')
+        self._assert_included(self.included_content, result)
+
+    def testIncludePrintModeHeading(self):
+        # In normal mode, 'heading' inserts a heading that is also a link to the
+        # included page -- hence the use of _assert_heading_link() in
+        # testIncludeHeading() above.  In print mode, it's just a heading -- no
+        # link.
+        heading = u'Look Out!'
+        result = self._execute(u'IncludedPage, "%s", 2' % heading)
+        match = self._assert_heading(2, heading, result)
+        self._assert_included(self.included_content, result, pos=match.end())
+
+    def testIncludePrintModeEditLink(self):
+        # In print mode, 'editlink' has no effect.
+        result = self._execute(u'IncludedPage,,, editlink')
+        match = self._assert_included(self.included_content, result)
+        assert u'[IncludedPage]' not in result
+        assert u'[edit]' not in result
+
+
+class TestIncludeMany(BaseTest):
+
+    included_pagename_1 = u'1_Intro'
+    included_content_1 = u'''\
+= Intro =
+
+This is an introduction.
+
+== Ooga ==
+
+Ooga-booga.  Ding-dong.
+
+== Argle ==
+
+Argle bargle snarky weeb.
+'''
+
+    included_pagename_2 = u'2_BlahBlah'
+    included_content_2 = u'''\
+= Blah Blah =
+
+more blah blah blah
+'''
+    included_pagename_3 = u'3_Conclusions'
+    included_content_3 = u'''\
+= Conclusions =
+
+Clearly, the snarky weeb rocks.
+'''
+
+    def setup_class(cls):
+        become_trusted(cls.request)
+        cls.includer_page = create_page(cls.request, cls.includer_pagename, u'Foo!')
+
+        create_page(cls.request, cls.included_pagename_1, cls.included_content_1)
+        create_page(cls.request, cls.included_pagename_2, cls.included_content_2)
+        create_page(cls.request, cls.included_pagename_3, cls.included_content_3)
+
+    def teardown_class(cls):
+        nuke_page(cls.request, cls.includer_pagename)
+        nuke_page(cls.request, cls.included_pagename_1)
+        nuke_page(cls.request, cls.included_pagename_2)
+        nuke_page(cls.request, cls.included_pagename_3)
+
+    def testIncludeMany(self):
+        # Include with a regex pulls in multiple pages.
+        result = self._execute(u'^\d_')
+        match = self._assert_heading(1, 'Intro', result)
+        match = self._assert_included('This is an introduction.', result, pos=match.end())
+        match = self._assert_heading(2, 'Ooga', result, pos=match.end())
+        match = self._assert_included('Ooga-booga.  Ding-dong.', result, pos=match.end())
+        match = self._assert_heading(2, 'Argle', result, pos=match.end())
+        match = self._assert_included('Argle bargle snarky weeb.', result, pos=match.end())
+        match = self._assert_heading(1, 'Blah Blah', result, pos=match.end())
+        match = self._assert_included('more blah blah blah', result, pos=match.end())
+        match = self._assert_heading(1, 'Conclusions', result, pos=match.end())
+        match = self._assert_included('Clearly, the snarky weeb rocks.', result, pos=match.end())
+
+    def testIncludeManySortDescending(self):
+        # sort=descending includes pages in reverse order
+        result = self._execute(u'^\d_,,, sort=descending')
+        match = self._assert_heading(1, 'Conclusions', result)
+        match = self._assert_included('Clearly, the snarky weeb rocks.', result, pos=match.end())
+        match = self._assert_heading(1, 'Blah Blah', result, pos=match.end())
+        match = self._assert_included('more blah blah blah', result, pos=match.end())
+        match = self._assert_heading(1, 'Intro', result, pos=match.end())
+        match = self._assert_included('This is an introduction.', result, pos=match.end())
+        match = self._assert_heading(2, 'Ooga', result, pos=match.end())
+        match = self._assert_included('Ooga-booga.  Ding-dong.', result, pos=match.end())
+        match = self._assert_heading(2, 'Argle', result, pos=match.end())
+        match = self._assert_included('Argle bargle snarky weeb.', result, pos=match.end())
+
+    def testIncludeManyMaxItems(self):
+        # items=N limits the include to N pages
+        result = self._execute(u'^\d_,,, items=2')
+        match = self._assert_heading(1, 'Intro', result)
+        match = self._assert_included('This is an introduction.', result, pos=match.end())
+        match = self._assert_heading(2, 'Ooga', result, pos=match.end())
+        match = self._assert_included('Ooga-booga.  Ding-dong.', result, pos=match.end())
+        match = self._assert_heading(2, 'Argle', result, pos=match.end())
+        match = self._assert_included('Argle bargle snarky weeb.', result, pos=match.end())
+        match = self._assert_heading(1, 'Blah Blah', result, pos=match.end())
+        match = self._assert_included('more blah blah blah', result, pos=match.end())
+        assert 'Conclusions' not in result
+        assert 'Clearly, the snarky weeb rocks.' not in result
+
+    def testIncludeManySkipItems(self):
+        # skipitems=N skips the first N matching pages
+        result = self._execute(u'^\d_,,, skipitems=2')
+        match = self._assert_heading(1, 'Conclusions', result)
+        match = self._assert_included('Clearly, the snarky weeb rocks.', result, pos=match.end())
+        assert 'Intro' not in result
+        assert 'Ooga-booga.  Ding-dong.' not in result
+        assert 'Blah Blah' not in result
+        assert 'more blah blah blah' not in result
+
+    def testIncludeManyMaxSkip(self):
+        # Combine 'items' and 'skipitems'.  Their combined behaviour is (IMHO) a
+        # bit weird: 'items' really means "the number of included pages to
+        # consider before skipping 'skipitems'".  Thus, "items=1, skipitems=1"
+        # will return an empty result: it considers only 1 page, and then skips
+        # past that page.
+        result = self._execute(u'^\d_,,, items=2, skipitems=1')
+        match = self._assert_heading(1, 'Blah Blah', result, pos=0)
+        match = self._assert_included('more blah blah blah', result, pos=match.end())
+        assert 'Intro' not in result
+        assert 'Conclusions' not in result
+
+        result = self._execute(u'^\d_,,, items=1, skipitems=1')
+        assert result == ''
+
+    def testIncludeManyTitlesOnly(self):
+        # titlesonly suppresses content, and just renders headers from the
+        # included pages
+        result = self._execute(u'^\d_,,, titlesonly')
+
+        # Hmmm: currently, Include links to the whole page for subheadings.
+        # Since this might be a bug, I pass href patterns that allow Include to
+        # change so it links to an anchor in the page.  If that is in fact the
+        # desired behaviour, those href patterns should make the anchor
+        # mandatory!
+
+        match = self._assert_link("./1_Intro", 'Intro', result)
+        match = self._assert_link("./1_Intro(#\S+)?", 'Ooga', result, pos=match.end())
+        match = self._assert_link("./1_Intro(#\S+)?", 'Argle', result, pos=match.end())
+        match = self._assert_link("./2_BlahBlah", 'Blah Blah', result, pos=match.end())
+        match = self._assert_link("./3_Conclusions", 'Conclusions', result, pos=match.end())
+
+        assert 'This is an introduction.' not in result
+        assert 'Ooga-booga.  Ding-dong.' not in result
+        assert 'Argle bargle snarky weeb.' not in result
+        assert 'more blah blah blah' not in result
+        assert 'Clearly, the snarky weeb rocks.' not in result
+
+
+coverage_modules = ['MoinMoin.macro.Include']
