1""" 2Python Markdown 3 4A Python implementation of John Gruber's Markdown. 5 6Documentation: https://python-markdown.github.io/ 7GitHub: https://github.com/Python-Markdown/markdown/ 8PyPI: https://pypi.org/project/Markdown/ 9 10Started by Manfred Stienstra (http://www.dwerg.net/). 11Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). 12Currently maintained by Waylan Limberg (https://github.com/waylan), 13Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). 14 15Copyright 2007-2018 The Python Markdown Project (v. 1.7 and later) 16Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) 17Copyright 2004 Manfred Stienstra (the original version) 18 19License: BSD (see LICENSE.md for details). 20 21Python-Markdown Regression Tests 22================================ 23 24Tests of the various APIs with the python markdown lib. 25""" 26 27import unittest 28import sys 29import os 30import markdown 31import warnings 32from markdown.__main__ import parse_options 33from logging import DEBUG, WARNING, CRITICAL 34import yaml 35import tempfile 36from io import BytesIO 37import xml.etree.ElementTree as etree 38from xml.etree.ElementTree import ProcessingInstruction 39 40 41class TestMarkdownBasics(unittest.TestCase): 42 """ Tests basics of the Markdown class. """ 43 44 def setUp(self): 45 """ Create instance of Markdown. """ 46 self.md = markdown.Markdown() 47 48 def testBlankInput(self): 49 """ Test blank input. """ 50 self.assertEqual(self.md.convert(''), '') 51 52 def testWhitespaceOnly(self): 53 """ Test input of only whitespace. """ 54 self.assertEqual(self.md.convert(' '), '') 55 56 def testSimpleInput(self): 57 """ Test simple input. """ 58 self.assertEqual(self.md.convert('foo'), '<p>foo</p>') 59 60 def testInstanceExtension(self): 61 """ Test Extension loading with a class instance. """ 62 from markdown.extensions.footnotes import FootnoteExtension 63 markdown.Markdown(extensions=[FootnoteExtension()]) 64 65 def testEntryPointExtension(self): 66 """ Test Extension loading with an entry point. """ 67 markdown.Markdown(extensions=['footnotes']) 68 69 def testDotNotationExtension(self): 70 """ Test Extension loading with Name (`path.to.module`). """ 71 markdown.Markdown(extensions=['markdown.extensions.footnotes']) 72 73 def testDotNotationExtensionWithClass(self): 74 """ Test Extension loading with class name (`path.to.module:Class`). """ 75 markdown.Markdown(extensions=['markdown.extensions.footnotes:FootnoteExtension']) 76 77 78class TestConvertFile(unittest.TestCase): 79 """ Tests of ConvertFile. """ 80 81 def setUp(self): 82 self.saved = sys.stdin, sys.stdout 83 sys.stdin = BytesIO(bytes('foo', encoding='utf-8')) 84 sys.stdout = BytesIO() 85 86 def tearDown(self): 87 sys.stdin, sys.stdout = self.saved 88 89 def getTempFiles(self, src): 90 """ Return the file names for two temp files. """ 91 infd, infile = tempfile.mkstemp(suffix='.txt') 92 with os.fdopen(infd, 'w') as fp: 93 fp.write(src) 94 outfd, outfile = tempfile.mkstemp(suffix='.html') 95 return infile, outfile, outfd 96 97 def testFileNames(self): 98 infile, outfile, outfd = self.getTempFiles('foo') 99 markdown.markdownFromFile(input=infile, output=outfile) 100 with os.fdopen(outfd, 'r') as fp: 101 output = fp.read() 102 self.assertEqual(output, '<p>foo</p>') 103 104 def testFileObjects(self): 105 infile = BytesIO(bytes('foo', encoding='utf-8')) 106 outfile = BytesIO() 107 markdown.markdownFromFile(input=infile, output=outfile) 108 outfile.seek(0) 109 self.assertEqual(outfile.read().decode('utf-8'), '<p>foo</p>') 110 111 def testStdinStdout(self): 112 markdown.markdownFromFile() 113 sys.stdout.seek(0) 114 self.assertEqual(sys.stdout.read().decode('utf-8'), '<p>foo</p>') 115 116 117class TestBlockParser(unittest.TestCase): 118 """ Tests of the BlockParser class. """ 119 120 def setUp(self): 121 """ Create instance of BlockParser. """ 122 self.parser = markdown.Markdown().parser 123 124 def testParseChunk(self): 125 """ Test BlockParser.parseChunk. """ 126 root = etree.Element("div") 127 text = 'foo' 128 self.parser.parseChunk(root, text) 129 self.assertEqual( 130 markdown.serializers.to_xhtml_string(root), 131 "<div><p>foo</p></div>" 132 ) 133 134 def testParseDocument(self): 135 """ Test BlockParser.parseDocument. """ 136 lines = ['#foo', '', 'bar', '', ' baz'] 137 tree = self.parser.parseDocument(lines) 138 self.assertIsInstance(tree, etree.ElementTree) 139 self.assertIs(etree.iselement(tree.getroot()), True) 140 self.assertEqual( 141 markdown.serializers.to_xhtml_string(tree.getroot()), 142 "<div><h1>foo</h1><p>bar</p><pre><code>baz\n</code></pre></div>" 143 ) 144 145 146class TestBlockParserState(unittest.TestCase): 147 """ Tests of the State class for BlockParser. """ 148 149 def setUp(self): 150 self.state = markdown.blockparser.State() 151 152 def testBlankState(self): 153 """ Test State when empty. """ 154 self.assertEqual(self.state, []) 155 156 def testSetSate(self): 157 """ Test State.set(). """ 158 self.state.set('a_state') 159 self.assertEqual(self.state, ['a_state']) 160 self.state.set('state2') 161 self.assertEqual(self.state, ['a_state', 'state2']) 162 163 def testIsSate(self): 164 """ Test State.isstate(). """ 165 self.assertEqual(self.state.isstate('anything'), False) 166 self.state.set('a_state') 167 self.assertEqual(self.state.isstate('a_state'), True) 168 self.state.set('state2') 169 self.assertEqual(self.state.isstate('state2'), True) 170 self.assertEqual(self.state.isstate('a_state'), False) 171 self.assertEqual(self.state.isstate('missing'), False) 172 173 def testReset(self): 174 """ Test State.reset(). """ 175 self.state.set('a_state') 176 self.state.reset() 177 self.assertEqual(self.state, []) 178 self.state.set('state1') 179 self.state.set('state2') 180 self.state.reset() 181 self.assertEqual(self.state, ['state1']) 182 183 184class TestHtmlStash(unittest.TestCase): 185 """ Test Markdown's HtmlStash. """ 186 187 def setUp(self): 188 self.stash = markdown.util.HtmlStash() 189 self.placeholder = self.stash.store('foo') 190 191 def testSimpleStore(self): 192 """ Test HtmlStash.store. """ 193 self.assertEqual(self.placeholder, self.stash.get_placeholder(0)) 194 self.assertEqual(self.stash.html_counter, 1) 195 self.assertEqual(self.stash.rawHtmlBlocks, ['foo']) 196 197 def testStoreMore(self): 198 """ Test HtmlStash.store with additional blocks. """ 199 placeholder = self.stash.store('bar') 200 self.assertEqual(placeholder, self.stash.get_placeholder(1)) 201 self.assertEqual(self.stash.html_counter, 2) 202 self.assertEqual( 203 self.stash.rawHtmlBlocks, 204 ['foo', 'bar'] 205 ) 206 207 def testReset(self): 208 """ Test HtmlStash.reset. """ 209 self.stash.reset() 210 self.assertEqual(self.stash.html_counter, 0) 211 self.assertEqual(self.stash.rawHtmlBlocks, []) 212 213 214class Item: 215 """ A dummy Registry item object for testing. """ 216 def __init__(self, data): 217 self.data = data 218 219 def __repr__(self): 220 return repr(self.data) 221 222 def __eq__(self, other): 223 return self.data == other 224 225 226class RegistryTests(unittest.TestCase): 227 """ Test the processor registry. """ 228 229 def testCreateRegistry(self): 230 r = markdown.util.Registry() 231 r.register(Item('a'), 'a', 20) 232 self.assertEqual(len(r), 1) 233 self.assertIsInstance(r, markdown.util.Registry) 234 235 def testRegisterWithoutPriority(self): 236 r = markdown.util.Registry() 237 with self.assertRaises(TypeError): 238 r.register(Item('a')) 239 240 def testSortRegistry(self): 241 r = markdown.util.Registry() 242 r.register(Item('a'), 'a', 20) 243 r.register(Item('b'), 'b', 21) 244 r.register(Item('c'), 'c', 20.5) 245 self.assertEqual(len(r), 3) 246 self.assertEqual(list(r), ['b', 'c', 'a']) 247 248 def testIsSorted(self): 249 r = markdown.util.Registry() 250 self.assertIs(r._is_sorted, False) 251 r.register(Item('a'), 'a', 20) 252 list(r) 253 self.assertIs(r._is_sorted, True) 254 r.register(Item('b'), 'b', 21) 255 self.assertIs(r._is_sorted, False) 256 r['a'] 257 self.assertIs(r._is_sorted, True) 258 r._is_sorted = False 259 r.get_index_for_name('a') 260 self.assertIs(r._is_sorted, True) 261 r._is_sorted = False 262 repr(r) 263 self.assertIs(r._is_sorted, True) 264 265 def testDeregister(self): 266 r = markdown.util.Registry() 267 r.register(Item('a'), 'a', 20) 268 r.register(Item('b'), 'b', 30) 269 r.register(Item('c'), 'c', 40) 270 self.assertEqual(len(r), 3) 271 r.deregister('b') 272 self.assertEqual(len(r), 2) 273 r.deregister('c', strict=False) 274 self.assertEqual(len(r), 1) 275 # deregister non-existant item with strict=False 276 r.deregister('d', strict=False) 277 self.assertEqual(len(r), 1) 278 with self.assertRaises(ValueError): 279 # deregister non-existant item with strict=True 280 r.deregister('e') 281 self.assertEqual(list(r), ['a']) 282 283 def testRegistryContains(self): 284 r = markdown.util.Registry() 285 item = Item('a') 286 r.register(item, 'a', 20) 287 self.assertIs('a' in r, True) 288 self.assertIn(item, r) 289 self.assertNotIn('b', r) 290 291 def testRegistryIter(self): 292 r = markdown.util.Registry() 293 r.register(Item('a'), 'a', 20) 294 r.register(Item('b'), 'b', 30) 295 self.assertEqual(list(r), ['b', 'a']) 296 297 def testRegistryGetItemByIndex(self): 298 r = markdown.util.Registry() 299 r.register(Item('a'), 'a', 20) 300 r.register(Item('b'), 'b', 30) 301 self.assertEqual(r[0], 'b') 302 self.assertEqual(r[1], 'a') 303 with self.assertRaises(IndexError): 304 r[3] 305 306 def testRegistryGetItemByItem(self): 307 r = markdown.util.Registry() 308 r.register(Item('a'), 'a', 20) 309 r.register(Item('b'), 'b', 30) 310 self.assertEqual(r['a'], 'a') 311 self.assertEqual(r['b'], 'b') 312 with self.assertRaises(KeyError): 313 r['c'] 314 315 def testRegistrySetItem(self): 316 r = markdown.util.Registry() 317 with self.assertRaises(TypeError): 318 r[0] = 'a' 319 # TODO: restore this when deprecated __setitem__ is removed. 320 # with self.assertRaises(TypeError): 321 # r['a'] = 'a' 322 # TODO: remove this when deprecated __setitem__ is removed. 323 with warnings.catch_warnings(record=True) as w: 324 warnings.simplefilter("always") 325 326 r['a'] = Item('a') 327 self.assertEqual(list(r), ['a']) 328 r['b'] = Item('b') 329 self.assertEqual(list(r), ['a', 'b']) 330 r['a'] = Item('a1') 331 self.assertEqual(list(r), ['a1', 'b']) 332 333 # Check the warnings 334 self.assertEqual(len(w), 3) 335 self.assertTrue(all(issubclass(x.category, DeprecationWarning) for x in w)) 336 337 def testRegistryDelItem(self): 338 r = markdown.util.Registry() 339 r.register(Item('a'), 'a', 20) 340 with self.assertRaises(KeyError): 341 del r[0] 342 # TODO: restore this when deprecated __del__ is removed. 343 # with self.assertRaises(TypeError): 344 # del r['a'] 345 # TODO: remove this when deprecated __del__ is removed. 346 with warnings.catch_warnings(record=True) as w: 347 warnings.simplefilter("always") 348 349 r.register(Item('b'), 'b', 15) 350 r.register(Item('c'), 'c', 10) 351 del r['b'] 352 self.assertEqual(list(r), ['a', 'c']) 353 del r['a'] 354 self.assertEqual(list(r), ['c']) 355 with self.assertRaises(KeyError): 356 del r['badname'] 357 del r['c'] 358 self.assertEqual(list(r), []) 359 360 # Check the warnings 361 self.assertEqual(len(w), 3) 362 self.assertTrue(all(issubclass(x.category, DeprecationWarning) for x in w)) 363 364 def testRegistrySlice(self): 365 r = markdown.util.Registry() 366 r.register(Item('a'), 'a', 20) 367 r.register(Item('b'), 'b', 30) 368 r.register(Item('c'), 'c', 40) 369 slc = r[1:] 370 self.assertEqual(len(slc), 2) 371 self.assertIsInstance(slc, markdown.util.Registry) 372 self.assertEqual(list(slc), ['b', 'a']) 373 374 def testGetIndexForName(self): 375 r = markdown.util.Registry() 376 r.register(Item('a'), 'a', 20) 377 r.register(Item('b'), 'b', 30) 378 self.assertEqual(r.get_index_for_name('a'), 1) 379 self.assertEqual(r.get_index_for_name('b'), 0) 380 with self.assertRaises(ValueError): 381 r.get_index_for_name('c') 382 383 def testRegisterDupplicate(self): 384 r = markdown.util.Registry() 385 r.register(Item('a'), 'a', 20) 386 r.register(Item('b1'), 'b', 10) 387 self.assertEqual(list(r), ['a', 'b1']) 388 self.assertEqual(len(r), 2) 389 r.register(Item('b2'), 'b', 30) 390 self.assertEqual(len(r), 2) 391 self.assertEqual(list(r), ['b2', 'a']) 392 393 def testRegistryDeprecatedAdd(self): 394 with warnings.catch_warnings(record=True) as w: 395 warnings.simplefilter("always") 396 397 r = markdown.util.Registry() 398 # Add first item 399 r.add('c', Item('c'), '_begin') 400 self.assertEqual(list(r), ['c']) 401 # Added to beginning 402 r.add('b', Item('b'), '_begin') 403 self.assertEqual(list(r), ['b', 'c']) 404 # Add before first item 405 r.add('a', Item('a'), '<b') 406 self.assertEqual(list(r), ['a', 'b', 'c']) 407 # Add before non-first item 408 r.add('a1', Item('a1'), '<b') 409 self.assertEqual(list(r), ['a', 'a1', 'b', 'c']) 410 # Add after non-last item 411 r.add('b1', Item('b1'), '>b') 412 self.assertEqual(list(r), ['a', 'a1', 'b', 'b1', 'c']) 413 # Add after last item 414 r.add('d', Item('d'), '>c') 415 self.assertEqual(list(r), ['a', 'a1', 'b', 'b1', 'c', 'd']) 416 # Add to end 417 r.add('e', Item('e'), '_end') 418 self.assertEqual(list(r), ['a', 'a1', 'b', 'b1', 'c', 'd', 'e']) 419 with self.assertRaises(ValueError): 420 r.add('f', Item('f'), 'badlocation') 421 422 # Check the warnings 423 self.assertEqual(len(w), 7) 424 self.assertTrue(all(issubclass(x.category, DeprecationWarning) for x in w)) 425 426 427class TestErrors(unittest.TestCase): 428 """ Test Error Reporting. """ 429 430 def setUp(self): 431 # Set warnings to be raised as errors 432 warnings.simplefilter('error') 433 434 def tearDown(self): 435 # Reset warning behavior back to default 436 warnings.simplefilter('default') 437 438 def testBadOutputFormat(self): 439 """ Test failure on bad output_format. """ 440 self.assertRaises(KeyError, markdown.Markdown, output_format='invalid') 441 442 def testLoadExtensionFailure(self): 443 """ Test failure of an extension to load. """ 444 self.assertRaises( 445 ImportError, 446 markdown.Markdown, extensions=['non_existant_ext'] 447 ) 448 449 def testLoadBadExtension(self): 450 """ Test loading of an Extension with no makeExtension function. """ 451 self.assertRaises(AttributeError, markdown.Markdown, extensions=['markdown.util']) 452 453 def testNonExtension(self): 454 """ Test loading a non Extension object as an extension. """ 455 self.assertRaises(TypeError, markdown.Markdown, extensions=[object]) 456 457 def testDotNotationExtensionWithBadClass(self): 458 """ Test Extension loading with non-existant class name (`path.to.module:Class`). """ 459 self.assertRaises( 460 AttributeError, 461 markdown.Markdown, 462 extensions=['markdown.extensions.footnotes:MissingExtension'] 463 ) 464 465 def testBaseExtention(self): 466 """ Test that the base Extension class will raise NotImplemented. """ 467 self.assertRaises( 468 NotImplementedError, 469 markdown.Markdown, extensions=[markdown.extensions.Extension()] 470 ) 471 472 473class testETreeComments(unittest.TestCase): 474 """ 475 Test that ElementTree Comments work. 476 477 These tests should only be a concern when using cElementTree with third 478 party serializers (including markdown's (x)html serializer). While markdown 479 doesn't use ElementTree.Comment itself, we should certainly support any 480 third party extensions which may. Therefore, these tests are included to 481 ensure such support is maintained. 482 """ 483 484 def setUp(self): 485 # Create comment node 486 self.comment = etree.Comment('foo') 487 488 def testCommentIsComment(self): 489 """ Test that an ElementTree Comment passes the `is Comment` test. """ 490 self.assertIs(self.comment.tag, etree.Comment) 491 492 def testCommentIsBlockLevel(self): 493 """ Test that an ElementTree Comment is recognized as BlockLevel. """ 494 md = markdown.Markdown() 495 self.assertIs(md.is_block_level(self.comment.tag), False) 496 497 def testCommentSerialization(self): 498 """ Test that an ElementTree Comment serializes properly. """ 499 self.assertEqual( 500 markdown.serializers.to_html_string(self.comment), 501 '<!--foo-->' 502 ) 503 504 def testCommentPrettify(self): 505 """ Test that an ElementTree Comment is prettified properly. """ 506 pretty = markdown.treeprocessors.PrettifyTreeprocessor(markdown.Markdown()) 507 pretty.run(self.comment) 508 self.assertEqual( 509 markdown.serializers.to_html_string(self.comment), 510 '<!--foo-->\n' 511 ) 512 513 514class testElementTailTests(unittest.TestCase): 515 """ Element Tail Tests """ 516 def setUp(self): 517 self.pretty = markdown.treeprocessors.PrettifyTreeprocessor(markdown.Markdown()) 518 519 def testBrTailNoNewline(self): 520 """ Test that last <br> in tree has a new line tail """ 521 root = etree.Element('root') 522 br = etree.SubElement(root, 'br') 523 self.assertEqual(br.tail, None) 524 self.pretty.run(root) 525 self.assertEqual(br.tail, "\n") 526 527 528class testSerializers(unittest.TestCase): 529 """ Test the html and xhtml serializers. """ 530 531 def testHtml(self): 532 """ Test HTML serialization. """ 533 el = etree.Element('div') 534 el.set('id', 'foo<&">') 535 p = etree.SubElement(el, 'p') 536 p.text = 'foo <&escaped>' 537 p.set('hidden', 'hidden') 538 etree.SubElement(el, 'hr') 539 non_element = etree.SubElement(el, None) 540 non_element.text = 'non-element text' 541 script = etree.SubElement(non_element, 'script') 542 script.text = '<&"test\nescaping">' 543 el.tail = "tail text" 544 self.assertEqual( 545 markdown.serializers.to_html_string(el), 546 '<div id="foo<&">">' 547 '<p hidden>foo <&escaped></p>' 548 '<hr>' 549 'non-element text' 550 '<script><&"test\nescaping"></script>' 551 '</div>tail text' 552 ) 553 554 def testXhtml(self): 555 """" Test XHTML serialization. """ 556 el = etree.Element('div') 557 el.set('id', 'foo<&">') 558 p = etree.SubElement(el, 'p') 559 p.text = 'foo<&escaped>' 560 p.set('hidden', 'hidden') 561 etree.SubElement(el, 'hr') 562 non_element = etree.SubElement(el, None) 563 non_element.text = 'non-element text' 564 script = etree.SubElement(non_element, 'script') 565 script.text = '<&"test\nescaping">' 566 el.tail = "tail text" 567 self.assertEqual( 568 markdown.serializers.to_xhtml_string(el), 569 '<div id="foo<&">">' 570 '<p hidden="hidden">foo<&escaped></p>' 571 '<hr />' 572 'non-element text' 573 '<script><&"test\nescaping"></script>' 574 '</div>tail text' 575 ) 576 577 def testMixedCaseTags(self): 578 """" Test preservation of tag case. """ 579 el = etree.Element('MixedCase') 580 el.text = 'not valid ' 581 em = etree.SubElement(el, 'EMPHASIS') 582 em.text = 'html' 583 etree.SubElement(el, 'HR') 584 self.assertEqual( 585 markdown.serializers.to_xhtml_string(el), 586 '<MixedCase>not valid <EMPHASIS>html</EMPHASIS><HR /></MixedCase>' 587 ) 588 589 def testProsessingInstruction(self): 590 """ Test serialization of ProcessignInstruction. """ 591 pi = ProcessingInstruction('foo', text='<&"test\nescaping">') 592 self.assertIs(pi.tag, ProcessingInstruction) 593 self.assertEqual( 594 markdown.serializers.to_xhtml_string(pi), 595 '<?foo <&"test\nescaping">?>' 596 ) 597 598 def testQNameTag(self): 599 """ Test serialization of QName tag. """ 600 div = etree.Element('div') 601 qname = etree.QName('http://www.w3.org/1998/Math/MathML', 'math') 602 math = etree.SubElement(div, qname) 603 math.set('display', 'block') 604 sem = etree.SubElement(math, 'semantics') 605 msup = etree.SubElement(sem, 'msup') 606 mi = etree.SubElement(msup, 'mi') 607 mi.text = 'x' 608 mn = etree.SubElement(msup, 'mn') 609 mn.text = '2' 610 ann = etree.SubElement(sem, 'annotations') 611 ann.text = 'x^2' 612 self.assertEqual( 613 markdown.serializers.to_xhtml_string(div), 614 '<div>' 615 '<math display="block" xmlns="http://www.w3.org/1998/Math/MathML">' 616 '<semantics>' 617 '<msup>' 618 '<mi>x</mi>' 619 '<mn>2</mn>' 620 '</msup>' 621 '<annotations>x^2</annotations>' 622 '</semantics>' 623 '</math>' 624 '</div>' 625 ) 626 627 def testQNameAttribute(self): 628 """ Test serialization of QName attribute. """ 629 div = etree.Element('div') 630 div.set(etree.QName('foo'), etree.QName('bar')) 631 self.assertEqual( 632 markdown.serializers.to_xhtml_string(div), 633 '<div foo="bar"></div>' 634 ) 635 636 def testBadQNameTag(self): 637 """ Test serialization of QName with no tag. """ 638 qname = etree.QName('http://www.w3.org/1998/Math/MathML') 639 el = etree.Element(qname) 640 self.assertRaises(ValueError, markdown.serializers.to_xhtml_string, el) 641 642 def testQNameEscaping(self): 643 """ Test QName escaping. """ 644 qname = etree.QName('<&"test\nescaping">', 'div') 645 el = etree.Element(qname) 646 self.assertEqual( 647 markdown.serializers.to_xhtml_string(el), 648 '<div xmlns="<&"test escaping">"></div>' 649 ) 650 651 def testQNamePreEscaping(self): 652 """ Test QName that is already partially escaped. """ 653 qname = etree.QName('<&"test escaping">', 'div') 654 el = etree.Element(qname) 655 self.assertEqual( 656 markdown.serializers.to_xhtml_string(el), 657 '<div xmlns="<&"test escaping">"></div>' 658 ) 659 660 def buildExtension(self): 661 """ Build an extension which registers fakeSerializer. """ 662 def fakeSerializer(elem): 663 # Ignore input and return hardcoded output 664 return '<div><p>foo</p></div>' 665 666 class registerFakeSerializer(markdown.extensions.Extension): 667 def extendMarkdown(self, md): 668 md.output_formats['fake'] = fakeSerializer 669 670 return registerFakeSerializer() 671 672 def testRegisterSerializer(self): 673 self.assertEqual( 674 markdown.markdown( 675 'baz', extensions=[self.buildExtension()], output_format='fake' 676 ), 677 '<p>foo</p>' 678 ) 679 680 def testXHTMLOutput(self): 681 self.assertEqual( 682 markdown.markdown('foo \nbar', output_format='xhtml'), 683 '<p>foo<br />\nbar</p>' 684 ) 685 686 def testHTMLOutput(self): 687 self.assertEqual( 688 markdown.markdown('foo \nbar', output_format='html'), 689 '<p>foo<br>\nbar</p>' 690 ) 691 692 693class testAtomicString(unittest.TestCase): 694 """ Test that AtomicStrings are honored (not parsed). """ 695 696 def setUp(self): 697 md = markdown.Markdown() 698 self.inlineprocessor = md.treeprocessors['inline'] 699 700 def testString(self): 701 """ Test that a regular string is parsed. """ 702 tree = etree.Element('div') 703 p = etree.SubElement(tree, 'p') 704 p.text = 'some *text*' 705 new = self.inlineprocessor.run(tree) 706 self.assertEqual( 707 markdown.serializers.to_html_string(new), 708 '<div><p>some <em>text</em></p></div>' 709 ) 710 711 def testSimpleAtomicString(self): 712 """ Test that a simple AtomicString is not parsed. """ 713 tree = etree.Element('div') 714 p = etree.SubElement(tree, 'p') 715 p.text = markdown.util.AtomicString('some *text*') 716 new = self.inlineprocessor.run(tree) 717 self.assertEqual( 718 markdown.serializers.to_html_string(new), 719 '<div><p>some *text*</p></div>' 720 ) 721 722 def testNestedAtomicString(self): 723 """ Test that a nested AtomicString is not parsed. """ 724 tree = etree.Element('div') 725 p = etree.SubElement(tree, 'p') 726 p.text = markdown.util.AtomicString('*some* ') 727 span1 = etree.SubElement(p, 'span') 728 span1.text = markdown.util.AtomicString('*more* ') 729 span2 = etree.SubElement(span1, 'span') 730 span2.text = markdown.util.AtomicString('*text* ') 731 span3 = etree.SubElement(span2, 'span') 732 span3.text = markdown.util.AtomicString('*here*') 733 span3.tail = markdown.util.AtomicString(' *to*') 734 span2.tail = markdown.util.AtomicString(' *test*') 735 span1.tail = markdown.util.AtomicString(' *with*') 736 new = self.inlineprocessor.run(tree) 737 self.assertEqual( 738 markdown.serializers.to_html_string(new), 739 '<div><p>*some* <span>*more* <span>*text* <span>*here*</span> ' 740 '*to*</span> *test*</span> *with*</p></div>' 741 ) 742 743 744class TestConfigParsing(unittest.TestCase): 745 def assertParses(self, value, result): 746 self.assertIs(markdown.util.parseBoolValue(value, False), result) 747 748 def testBooleansParsing(self): 749 self.assertParses(True, True) 750 self.assertParses('novalue', None) 751 self.assertParses('yES', True) 752 self.assertParses('FALSE', False) 753 self.assertParses(0., False) 754 self.assertParses('none', False) 755 756 def testPreserveNone(self): 757 self.assertIsNone(markdown.util.parseBoolValue('None', preserve_none=True)) 758 self.assertIsNone(markdown.util.parseBoolValue(None, preserve_none=True)) 759 760 def testInvalidBooleansParsing(self): 761 self.assertRaises(ValueError, markdown.util.parseBoolValue, 'novalue') 762 763 764class TestCliOptionParsing(unittest.TestCase): 765 """ Test parsing of Command Line Interface Options. """ 766 767 def setUp(self): 768 self.default_options = { 769 'input': None, 770 'output': None, 771 'encoding': None, 772 'output_format': 'xhtml', 773 'lazy_ol': True, 774 'extensions': [], 775 'extension_configs': {}, 776 } 777 self.tempfile = '' 778 779 def tearDown(self): 780 if os.path.isfile(self.tempfile): 781 os.remove(self.tempfile) 782 783 def testNoOptions(self): 784 options, logging_level = parse_options([]) 785 self.assertEqual(options, self.default_options) 786 self.assertEqual(logging_level, CRITICAL) 787 788 def testQuietOption(self): 789 options, logging_level = parse_options(['-q']) 790 self.assertGreater(logging_level, CRITICAL) 791 792 def testVerboseOption(self): 793 options, logging_level = parse_options(['-v']) 794 self.assertEqual(logging_level, WARNING) 795 796 def testNoisyOption(self): 797 options, logging_level = parse_options(['--noisy']) 798 self.assertEqual(logging_level, DEBUG) 799 800 def testInputFileOption(self): 801 options, logging_level = parse_options(['foo.txt']) 802 self.default_options['input'] = 'foo.txt' 803 self.assertEqual(options, self.default_options) 804 805 def testOutputFileOption(self): 806 options, logging_level = parse_options(['-f', 'foo.html']) 807 self.default_options['output'] = 'foo.html' 808 self.assertEqual(options, self.default_options) 809 810 def testInputAndOutputFileOptions(self): 811 options, logging_level = parse_options(['-f', 'foo.html', 'foo.txt']) 812 self.default_options['output'] = 'foo.html' 813 self.default_options['input'] = 'foo.txt' 814 self.assertEqual(options, self.default_options) 815 816 def testEncodingOption(self): 817 options, logging_level = parse_options(['-e', 'utf-8']) 818 self.default_options['encoding'] = 'utf-8' 819 self.assertEqual(options, self.default_options) 820 821 def testOutputFormatOption(self): 822 options, logging_level = parse_options(['-o', 'html']) 823 self.default_options['output_format'] = 'html' 824 self.assertEqual(options, self.default_options) 825 826 def testNoLazyOlOption(self): 827 options, logging_level = parse_options(['-n']) 828 self.default_options['lazy_ol'] = False 829 self.assertEqual(options, self.default_options) 830 831 def testExtensionOption(self): 832 options, logging_level = parse_options(['-x', 'markdown.extensions.footnotes']) 833 self.default_options['extensions'] = ['markdown.extensions.footnotes'] 834 self.assertEqual(options, self.default_options) 835 836 def testMultipleExtensionOptions(self): 837 options, logging_level = parse_options([ 838 '-x', 'markdown.extensions.footnotes', 839 '-x', 'markdown.extensions.smarty' 840 ]) 841 self.default_options['extensions'] = [ 842 'markdown.extensions.footnotes', 843 'markdown.extensions.smarty' 844 ] 845 self.assertEqual(options, self.default_options) 846 847 def create_config_file(self, config): 848 """ Helper to create temp config files. """ 849 if not isinstance(config, str): 850 # convert to string 851 config = yaml.dump(config) 852 fd, self.tempfile = tempfile.mkstemp('.yml') 853 with os.fdopen(fd, 'w') as fp: 854 fp.write(config) 855 856 def testExtensionConfigOption(self): 857 config = { 858 'markdown.extensions.wikilinks': { 859 'base_url': 'http://example.com/', 860 'end_url': '.html', 861 'html_class': 'test', 862 }, 863 'markdown.extensions.footnotes:FootnotesExtension': { 864 'PLACE_MARKER': '~~~footnotes~~~' 865 } 866 } 867 self.create_config_file(config) 868 options, logging_level = parse_options(['-c', self.tempfile]) 869 self.default_options['extension_configs'] = config 870 self.assertEqual(options, self.default_options) 871 872 def textBoolExtensionConfigOption(self): 873 config = { 874 'markdown.extensions.toc': { 875 'title': 'Some Title', 876 'anchorlink': True, 877 'permalink': True 878 } 879 } 880 self.create_config_file(config) 881 options, logging_level = parse_options(['-c', self.tempfile]) 882 self.default_options['extension_configs'] = config 883 self.assertEqual(options, self.default_options) 884 885 def testExtensionConfigOptionAsJSON(self): 886 config = { 887 'markdown.extensions.wikilinks': { 888 'base_url': 'http://example.com/', 889 'end_url': '.html', 890 'html_class': 'test', 891 }, 892 'markdown.extensions.footnotes:FootnotesExtension': { 893 'PLACE_MARKER': '~~~footnotes~~~' 894 } 895 } 896 import json 897 self.create_config_file(json.dumps(config)) 898 options, logging_level = parse_options(['-c', self.tempfile]) 899 self.default_options['extension_configs'] = config 900 self.assertEqual(options, self.default_options) 901 902 def testExtensionConfigOptionMissingFile(self): 903 self.assertRaises(IOError, parse_options, ['-c', 'missing_file.yaml']) 904 905 def testExtensionConfigOptionBadFormat(self): 906 config = """ 907[footnotes] 908PLACE_MARKER= ~~~footnotes~~~ 909""" 910 self.create_config_file(config) 911 self.assertRaises(yaml.YAMLError, parse_options, ['-c', self.tempfile]) 912 913 914class TestEscapeAppend(unittest.TestCase): 915 """ Tests escape character append. """ 916 917 def testAppend(self): 918 """ Test that appended escapes are only in the current instance. """ 919 md = markdown.Markdown() 920 md.ESCAPED_CHARS.append('|') 921 self.assertEqual('|' in md.ESCAPED_CHARS, True) 922 md2 = markdown.Markdown() 923 self.assertEqual('|' not in md2.ESCAPED_CHARS, True) 924 925 926class TestBlockAppend(unittest.TestCase): 927 """ Tests block kHTML append. """ 928 929 def testBlockAppend(self): 930 """ Test that appended escapes are only in the current instance. """ 931 md = markdown.Markdown() 932 md.block_level_elements.append('test') 933 self.assertEqual('test' in md.block_level_elements, True) 934 md2 = markdown.Markdown() 935 self.assertEqual('test' not in md2.block_level_elements, True) 936 937 938class TestAncestorExclusion(unittest.TestCase): 939 """ Tests exclusion of tags in ancestor list. """ 940 941 class AncestorExample(markdown.inlinepatterns.SimpleTagInlineProcessor): 942 """ Ancestor Test. """ 943 944 ANCESTOR_EXCLUDES = ('a',) 945 946 def handleMatch(self, m, data): 947 """ Handle match. """ 948 el = etree.Element(self.tag) 949 el.text = m.group(2) 950 return el, m.start(0), m.end(0) 951 952 class AncestorExtension(markdown.Extension): 953 954 def __init__(self, *args, **kwargs): 955 """Initialize.""" 956 957 self.config = {} 958 959 def extendMarkdown(self, md): 960 """Modify inline patterns.""" 961 962 pattern = r'(\+)([^\+]+)\1' 963 md.inlinePatterns.register(TestAncestorExclusion.AncestorExample(pattern, 'strong'), 'ancestor-test', 0) 964 965 def setUp(self): 966 """Setup markdown object.""" 967 self.md = markdown.Markdown(extensions=[TestAncestorExclusion.AncestorExtension()]) 968 969 def test_ancestors(self): 970 """ Test that an extension can exclude parent tags. """ 971 test = """ 972Some +test+ and a [+link+](http://test.com) 973""" 974 result = """<p>Some <strong>test</strong> and a <a href="http://test.com">+link+</a></p>""" 975 976 self.md.reset() 977 self.assertEqual(self.md.convert(test), result) 978 979 def test_ancestors_tail(self): 980 """ Test that an extension can exclude parent tags when dealing with a tail. """ 981 test = """ 982[***+em+*+strong+**](http://test.com) 983""" 984 result = """<p><a href="http://test.com"><strong><em>+em+</em>+strong+</strong></a></p>""" 985 986 self.md.reset() 987 self.assertEqual(self.md.convert(test), result) 988 989 990class TestGeneralDeprecations(unittest.TestCase): 991 """Test general deprecations.""" 992 993 def test_version_deprecation(self): 994 """Test that version is deprecated.""" 995 996 with warnings.catch_warnings(record=True) as w: 997 # Cause all warnings to always be triggered. 998 warnings.simplefilter("always") 999 # Trigger a warning. 1000 version = markdown.version 1001 # Verify some things 1002 self.assertEqual(len(w), 1) 1003 self.assertTrue(issubclass(w[-1].category, DeprecationWarning)) 1004 self.assertEqual(version, markdown.__version__) 1005 1006 def test_version_info_deprecation(self): 1007 """Test that version info is deprecated.""" 1008 1009 with warnings.catch_warnings(record=True) as w: 1010 # Cause all warnings to always be triggered. 1011 warnings.simplefilter("always") 1012 # Trigger a warning. 1013 version_info = markdown.version_info 1014 # Verify some things 1015 self.assertEqual(len(w), 1) 1016 self.assertTrue(issubclass(w[-1].category, DeprecationWarning)) 1017 self.assertEqual(version_info, markdown.__version_info__) 1018 1019 def test_deprecation_wrapper_dir(self): 1020 """Tests the `__dir__` attribute of the class as it replaces the module's.""" 1021 1022 dir_attr = dir(markdown) 1023 self.assertNotIn('version', dir_attr) 1024 self.assertIn('__version__', dir_attr) 1025 self.assertNotIn('version_info', dir_attr) 1026 self.assertIn('__version_info__', dir_attr) 1027