From 1a0463b3ac390e601fde16d003a9a744936ce852 Mon Sep 17 00:00:00 2001 From: facelessuser Date: Sat, 10 Sep 2022 15:50:19 -0600 Subject: [PATCH] more tests --- docs/src/dictionary/en-custom.txt | 1 + pymdownx/blocks/__init__.py | 2 +- pymdownx/blocks/block.py | 16 +- .../test_blocks/test_general_blocks.py | 388 ++++++++++++++++++ 4 files changed, 397 insertions(+), 10 deletions(-) diff --git a/docs/src/dictionary/en-custom.txt b/docs/src/dictionary/en-custom.txt index aa091bed20..808fd37eb4 100644 --- a/docs/src/dictionary/en-custom.txt +++ b/docs/src/dictionary/en-custom.txt @@ -185,6 +185,7 @@ ned nl octocat online +parser's plugin plugins plusminus diff --git a/pymdownx/blocks/__init__.py b/pymdownx/blocks/__init__.py index fa8ed675f7..366b9634cd 100644 --- a/pymdownx/blocks/__init__.py +++ b/pymdownx/blocks/__init__.py @@ -253,9 +253,9 @@ def split_header(self, block, blocks, length): # Move block ending to be parsed later if m: - block = block[:m.start(0)] end = block[m.start(0):] blocks.insert(0, end) + block = block[:m.start(0)] # More formal YAML config start = RE_YAML_START.match(block.strip('\n')) diff --git a/pymdownx/blocks/block.py b/pymdownx/blocks/block.py index 778e145398..4445f239c7 100644 --- a/pymdownx/blocks/block.py +++ b/pymdownx/blocks/block.py @@ -104,7 +104,7 @@ def type_string_insensitive(value): def type_html_attribute_value(value): """Ensure type HTML attribute value or fail.""" - return type_string(value).replace('"', '"e;') + return type_string(value).replace('"', '"') def type_html_attribute_name(value): @@ -117,15 +117,15 @@ def _delimiter(string, split, string_type): """Split the string by the delimiter and then parse with the parser.""" l = [] + # Ensure input is a string + string = type_string(string) for s in string.split(split): s = s.strip() if not s: continue - try: - s = string_type(s) - l.append(s) - except Exception: - pass + # Ensure each part conforms to the desired string type + s = string_type(s) + l.append(s) return l @@ -264,7 +264,7 @@ def parse_config(self, args, **options): # A `None` for a parser means accept as is. total_parsers = len(parsers) for e, a in enumerate(arguments): - parser = parsers[e] if e <= total_parsers else parsers[-1] + parser = parsers[e] if e < total_parsers else parsers[-1] if parser is not None: try: a = parser(a) @@ -278,8 +278,6 @@ def parse_config(self, args, **options): spec = self.option_spec parsed = {} for k, v in spec.items(): - if not k: - continue parsed[k] = v[0] # Parse provided options diff --git a/tests/test_extensions/test_blocks/test_general_blocks.py b/tests/test_extensions/test_blocks/test_general_blocks.py index 84825baf50..6a1135755f 100644 --- a/tests/test_extensions/test_blocks/test_general_blocks.py +++ b/tests/test_extensions/test_blocks/test_general_blocks.py @@ -2,6 +2,8 @@ from ... import util import unittest from pymdownx.blocks import block +import xml.etree.ElementTree as etree +import markdown class TestTypeFunctions(unittest.TestCase): @@ -43,9 +45,22 @@ def test_type_ranged_number(self): def test_type_ranged_integer(self): """Test `type_ranged_integer`.""" + self.assertEqual(4, block.type_ranged_integer(3, 8)(4)) + self.assertEqual(4, block.type_ranged_integer(3, 8)(4.0)) + with self.assertRaises(ValueError): + block.type_ranged_integer(3, 8)(4.3) + with self.assertRaises(ValueError): + block.type_ranged_integer(3, 8)(2) + with self.assertRaises(ValueError): + block.type_ranged_integer(3, 8)(9) + def test_type_tag(self): """Test `type_tag`.""" + self.assertEqual('div', block.type_tag('div')) + with self.assertRaises(ValueError): + block.type_tag('3bad') + def test_type_boolean(self): """Test `type_boolean`.""" @@ -80,24 +95,397 @@ def test_type_string_insensitive(self): def test_type_html_attribute_value(self): """Test `type_html_attribute_value`.""" + self.assertEqual('some-value', block.type_html_attribute_value('some-value')) + self.assertEqual('"some-value"', block.type_html_attribute_value('"some-value"')) + with self.assertRaises(ValueError): + block.type_html_attribute_value(3) + def test_type_html_attribute_name(self): """Test `type_html_attribute_name`.""" + self.assertEqual('attribute', block.type_html_attribute_name('attribute')) + self.assertEqual('attr_ibute', block.type_html_attribute_name('attr@ibute')) + def test_type_string_in(self): """Test `type_string_in`.""" + self.assertEqual('this', block.type_string_in(['this', 'that'])('this')) + with self.assertRaises(ValueError): + block.type_string_in(['this', 'that'])('bad') + def test_type_string_delimiter(self): """Test `type_string_delimiter`.""" + self.assertEqual(['this', 'that'], block.type_string_delimiter(';')('this; that')) + self.assertEqual(['this', 'that'], block.type_string_delimiter(' ')('this that')) + def test_type_attribute_dict(self): """Test `type_attribute_dict`.""" + self.assertEqual( + {'test': "test", "class": ["one", "two", "three"]}, + block.type_attribute_dict({'test': "test", "class": "one two three"}) + ) + + with self.assertRaises(ValueError): + block.type_attribute_dict(3) + def test_type_class(self): """Test `type_class`.""" + self.assertEqual('this', block.type_class('this')) + with self.assertRaises(ValueError): + block.type_class('this that') + def test_type_classes(self): """Test `type_classes`.""" + self.assertEqual(['this', 'that'], block.type_classes('this that')) + + +class TestGeneral(unittest.TestCase): + """Test general cases.""" + + def test_attribute_override(self): + """Test that attributes cannot be overridden.""" + + class AttrOverride(block.Block): + NAME = 'override' + + OPTIONS = { + 'attributes': [False, block.type_boolean], + } + + def on_create(self, parent): + """Create.""" + + return etree.SubElement(parent, 'div') + + with self.assertRaises(ValueError): + markdown.markdown( + '/// override\n///', + extensions=['pymdownx.blocks'], + extension_configs={'pymdownx.blocks': {'blocks': [AttrOverride]}} + ) + + +class TestBlockUndefinedOption(util.MdCase): + """Test Blocks with undefined options.""" + + class UndefinedBlock(block.Block): + """Undefined option block.""" + + NAME = 'undefined' + + def on_create(self, parent): + """Create.""" + + return etree.SubElement(parent, 'div') + + extension = ['pymdownx.blocks'] + extension_configs = {'pymdownx.blocks': {'blocks': [UndefinedBlock]}} + + def test_undefined_option(self): + """An undefined option will cause the block parsing to fail.""" + + self.check_markdown( + R''' + /// undefined + option: whatever + + content + /// + ''', + ''' +

/// undefined + option: whatever

+

content + ///

+ ''', + True + ) + + +class TestMiscCases(util.MdCase): + """Test some miscellaneous cases.""" + + class MiscBlock(block.Block): + """A miscellaneous block.""" + + NAME = 'misc' + OPTIONS = {'test': ['whatever', block.type_string]} + + def on_create(self, parent): + """Create.""" + + return etree.SubElement(parent, 'div', {'test': self.options['test']}) + + extension = ['pymdownx.blocks'] + extension_configs = {'pymdownx.blocks': {'blocks': [MiscBlock]}} + + def test_general_config(self): + """Test that content block should have a blank line between config.""" + + self.check_markdown( + R''' + /// misc + --- + test: misc + --- + + content + /// + ''', + ''' +
+

content

+
+ ''', + True + ) + + def test_no_content(self): + """Test config and no content.""" + + self.check_markdown( + R''' + /// misc + --- + test: misc + --- + /// + ''', + ''' +
+ ''', + True + ) + + def test_unfenced_config_and_no_content(self): + """Test no fenced config and no content.""" + + self.check_markdown( + R''' + /// misc + test: misc + /// + ''', + ''' +
+ ''', + True + ) + + def test_content_after_only_config_block(self): + """Test content after only config block.""" + + self.check_markdown( + R''' + /// misc + test: misc + /// + more + ''', + ''' +
+

more

+ ''', + True + ) + + +class TestBadArgOptionParsers(util.MdCase): + """Test when a block's options do not fulfill the parser's expectations.""" + + class FailBlock(block.Block): + """Test failure to satisfy parser.""" + + NAME = 'fail' + ARGUMENTS = {'required': 1, 'parsers': [block.type_tag]} + OPTIONS = {'test': ['tag', block.type_tag]} + + def on_create(self, parent): + """Create.""" + + return etree.SubElement(parent, 'div') + + extension = ['pymdownx.blocks'] + extension_configs = {'pymdownx.blocks': {'blocks': [FailBlock]}} + + def test_fail_args(self): + """Test failure of arguments.""" + + self.check_markdown( + R''' + /// fail | 3tag + /// + ''', + ''' +

/// fail | 3tag + ///

+ ''', + True + ) + + def test_fail_opts(self): + """Test failure of options.""" + + self.check_markdown( + R''' + /// fail | tag + test: 3tag + + content + /// + ''', + ''' +

/// fail | tag + test: 3tag

+

content + ///

+ ''', + True + ) + + def test_bad_config(self): + """Test when content is used in place of config.""" + + self.check_markdown( + R''' + /// fail | tag + content + /// + ''', + ''' +

/// fail | tag + content + ///

+ ''', + True + ) + + def test_bad_yaml(self): + """Test config is bad YAML.""" + + self.check_markdown( + R''' + /// fail | tag + : + /// + ''', + ''' +

/// fail | tag + : + ///

+ ''', + True + ) + + def test_missing_config_end(self): + """Test when config has YAML start but no end.""" + + self.check_markdown( + R''' + /// fail | tag + --- + test: tag + + content + /// + ''', + ''' +

/// fail | tag

+

test: tag

+

content + ///

+ ''', + True + ) + + def test_no_space_between_config(self): + """Test that content block should have a blank line between config.""" + + self.check_markdown( + R''' + /// fail | tag + --- + test: tag + --- + content + /// + ''', + ''' +

/// fail | tag

+

test: tag

+

content + ///

+ ''', + True + ) + + +class TestBlockSplit(util.MdCase): + """Test Blocks that split arguments.""" + + class SplitBlock(block.Block): + """Split block.""" + + NAME = 'split' + + ARGUMENTS = {'required': 2} + + def on_create(self, parent): + """Create.""" + + return etree.SubElement(parent, 'div', {self.args[0]: '0', self.args[1]: '1'}) + + extension = ['pymdownx.blocks'] + extension_configs = {'pymdownx.blocks': {'blocks': [SplitBlock]}} + + def test_split(self): + """Test that attributes cannot be overridden.""" + + self.check_markdown( + R''' + /// split | this that + /// + ''', + ''' +
+ ''', + True + ) + + def test_split_less(self): + """Test failure when arguments are too few.""" + + self.check_markdown( + R''' + /// split | this + /// + ''', + ''' +

/// split | this + ///

+ ''', + True + ) + + def test_split_greater(self): + """Test failure when arguments are too many.""" + + self.check_markdown( + R''' + /// split | this that another + /// + ''', + ''' +

/// split | this that another + ///

+ ''', + True + ) + class TestBlocksTab(util.MdCase): """Test Blocks tab cases."""