text: import pytest
  from pytest import raises
  from .utils import staticmethods
  
  skip = pytest.mark.skip
  xfail = pytest.mark.xfail
  
  from markup.src import html, nodes, parse
  
  class TestNode(nodes.Node):
      tag = 'test'
      params = {'foo?': False, 'bar?': False}
  
      def make_attributes(self) -> html.Attributes:
          return self.attributes | {'data': html.format_attributes(self.data)}
  
  
  class ErrorNode(nodes.Node):
      def make_data(self, data: html.Attributes) -> html.Attributes:
          raise nodes.MarkupError('This always errors')
  
  
  markup = parse.Markup()
  markup.nodes['$']['test'] = TestNode
  markup.nodes['$']['error'] = ErrorNode
  
  error_msg = '<span class="error">&lt;{}&gt;</span>'
  
  
  @staticmethods
  class Test_Markup_parse:
      def test_resolves_escaping():
          assert markup.parse(r'\n\\\ \"') == '\n\\ "'
  
  
  @staticmethods
  class Test_Markup_parse_string:
      def test_accepts_any_character_without_alphabet_or_exclude():
          assert markup.parse_string('abc') == ('abc', '')
  
      def test_only_accepts_characters_in_alphabet_if_given():
          assert markup.parse_string('abc', alphabet='ab') == ('ab', 'c')
  
      def test_does_not_accept_characters_in_exclude():
          assert markup.parse_string('abc', exclude='c') == ('ab', 'c')
          assert markup.parse_string('abc', alphabet='abc', exclude='c') == ('ab', 'c')
  
      def test_allows_escaped_characters_if_backslash_accepted():
          assert markup.parse_string('a\\nb') == ('a\\nb', '')
          assert markup.parse_string('a\\nb', exclude='\\') == ('a', '\\nb')
  
      def test_raises_ParseError_if_string_ends_after_backslash():
          with raises(parse.ParseError):
              markup.parse_string('\\')
  
      def test_allows_embedded_nodes_if_node_prefix_accepted():
          assert markup.parse_string('a $test b') == ('a <test></test> b', '')
          assert markup.parse_string('a $test b', exclude='$') == ('a ', '$test b')
  
      def test_raises_ParseError_if_nothing_parsed_and_error_message_given():
          with raises(parse.ParseError, match='^Foo$'):
              markup.parse_string('abc', exclude='a', error_msg='Foo')
  
          markup.parse_string('abc', exclude='a') == ('', 'abc')
  
  
  @staticmethods
  class Test_Markup_parse_node:
      def test_raises_ParseError_if_prefix_is_not_followed_by_valid_character():
          with raises(parse.ParseError):
              markup.parse_node('$ ')
  
      def test_raises_ParseError_if_hash_is_not_followed_by_valid_character():
          with raises(parse.ParseError):
              markup.parse_node('$test# ')
  
      def test_raises_ParseError_if_dot_is_not_followed_by_valid_character():
          with raises(parse.ParseError):
              markup.parse_node('$test. ')
  
      def test_multiple_ids_are_not_accepted():
          assert markup.parse_node('$test#foo#bar') == ('<test id="foo"></test>', '#bar')
  
      def test_multiple_classes_are_accepted():
          assert markup.parse_node('$test.foo.bar') == ('<test class="foo bar"></test>', '')
  
      def test_data_ignores_whitespace():
          assert markup.parse_node('$test[foo\n\n\nbar]') == ('<test data="foo bar"></test>', '')
  
      def test_data_doesnt_allow_embedded_nodes():
          with raises(parse.ParseError):
              markup.parse_node('$test[foo $test bar]')
  
      def test_text_ignores_whitespace():
          assert markup.parse_node('$test{foo\n\n\nbar}') == ('<test>foo bar</test>', '')
  
      def test_text_allows_embedded_nodes():
          assert markup.parse_node('$test{foo $test bar}') == ('<test>foo <test></test> bar</test>', '')
  
      def test_returns_error_span_if_unknown_node():
          assert markup.parse_node('$foo') == (error_msg.format('Invalid node: $foo'), '')
  
      def test_returns_error_span_if_handler_errors():
          assert markup.parse_node('$error') == (error_msg.format('This always errors'), '')
  
  
  @staticmethods
  class Test_Markup_parse_list:
      def test_accepts_characters_until_end_character():
          assert markup.parse_list('abc]def', end=']') == (['abc'], ']def')
  
      def test_skips_whitespace():
          assert markup.parse_list(' \r\n\t\v') == ([], '')
  
      def test_groups_quoted_characters():
          assert markup.parse_list('"a b\nc"') == (['a b\nc'], '')
  
      def test_doesnt_stop_at_end_character_inside_quotes():
          assert markup.parse_list('"a]b"]', end=']') == (['a]b'], ']')
  
      def test_groups_contiguous_non_whitespace():
          assert markup.parse_list('abc') == (['abc'], '')
  
      def test_separates_non_contiguous_non_whitespace():
          assert markup.parse_list('a b c') == (['a', 'b', 'c'], '')
  
      def test_disallows_escaping_if_backslash_excluded():
          assert markup.parse_list('a\\nb') == (['a\\nb'], '')
          with raises(parse.ParseError):
              markup.parse_list('a\\nb', exclude='\\')
  
      def test_disallows_embedding_if_prefix_characters_excluded():
          assert markup.parse_list('$test') == (['<test></test>'], '')
  
          with raises(parse.ParseError):
              markup.parse_list('$test', exclude='$')
  