Created
June 7, 2026 23:33
-
-
Save davepeck/32c096daeab71d2040ff396c61da58bf to your computer and use it in GitHub Desktop.
For discussion: proposed new parser tests
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import pytest | |
| from .htmlspec import Namespace # proposed; new -- has .HTML, .SVG, and .MATH | |
| from .parser import TemplateParser | |
| from .template_utils import TemplateRef | |
| from .tnodes import ( | |
| TComponent, | |
| TElement, | |
| TInterpolatedAttribute, | |
| TLiteralAttribute, | |
| TSpreadAttribute, | |
| TTemplatedAttribute, | |
| ) | |
| def test_void_with_no_attributes_no_whitespace(): | |
| node = TemplateParser.parse(t"<br/>") | |
| assert node == TElement("br") | |
| def test_void_with_no_attributes_whitespace(): | |
| node = TemplateParser.parse(t"<br />") | |
| assert node == TElement("br") | |
| def test_void_with_unquoted_end_attribute_no_whitespace(): | |
| node = TemplateParser.parse(t"<br x=a/>") | |
| assert node == TElement("br", attrs=(TLiteralAttribute(name="x", value="a/"),)) | |
| def test_void_with_unquoted_end_attribute_whitespace(): | |
| node = TemplateParser.parse(t"<br x=a />") | |
| assert node == TElement("br", attrs=(TLiteralAttribute(name="x", value="a"),)) | |
| def test_void_with_interpolated_end_attribute_no_whitespace(): | |
| a = "wow" | |
| node = TemplateParser.parse(t"<br x={a}/>") | |
| assert node == TElement( | |
| "br", | |
| attrs=( | |
| TTemplatedAttribute( | |
| name="x", value_ref=TemplateRef(strings=("", "/"), i_indexes=(0,)) | |
| ), | |
| ), | |
| ) | |
| def test_void_with_interpolated_end_attribute_whitespace(): | |
| a = "wow" | |
| node = TemplateParser.parse(t"<br x={a} />") | |
| assert node == TElement( | |
| "br", attrs=(TInterpolatedAttribute(name="x", value_i_index=0),) | |
| ) | |
| def test_void_with_quoted_end_attribute_no_whitespace(): | |
| node = TemplateParser.parse(t'<br x="a"/>') | |
| assert node == TElement("br", attrs=(TLiteralAttribute(name="x", value="a"),)) | |
| def test_void_with_boolean_end_attribute_no_whitespace(): | |
| node = TemplateParser.parse(t"<br x/>") | |
| assert node == TElement("br", attrs=(TLiteralAttribute(name="x", value=None),)) | |
| def test_void_with_spread_attribute_no_whitespace(): | |
| attrs = {"x": "a"} | |
| node = TemplateParser.parse(t"<br {attrs}/>") | |
| assert node == TElement("br", attrs=(TSpreadAttribute(i_index=0),)) | |
| def test_non_void_with_no_attributes_no_whitespace(): | |
| with pytest.raises(ValueError): | |
| # Raises because this is not a void tag, so this syntax is illegal | |
| _ = TemplateParser.parse(t"<div/>") | |
| def test_non_void_with_no_attributes_whitespace(): | |
| with pytest.raises(ValueError): | |
| # Raises because this is not a void tag, so this syntax is illegal | |
| _ = TemplateParser.parse(t"<div />") | |
| def test_non_void_with_unquoted_end_attribute_no_whitespace(): | |
| with pytest.raises(ValueError): | |
| # Raises because this is an open tag without a matching close tag | |
| _ = TemplateParser.parse(t"<div x=a/>") | |
| def test_non_void_with_unquoted_end_attribute_whitespace(): | |
| with pytest.raises(ValueError): | |
| # Raises because this is not a void tag, so this syntax is illegal | |
| _ = TemplateParser.parse(t"<div x=a />") | |
| def test_non_void_with_unquoted_end_attribute_no_whitespace_closed(): | |
| node = TemplateParser.parse(t"<div x=a/></div>") | |
| assert node == TElement("div", attrs=(TLiteralAttribute(name="x", value="a/"),)) | |
| def test_non_void_with_interpolated_end_attribute_no_whitespace(): | |
| a = "wow" | |
| with pytest.raises(ValueError): | |
| # Raises because this is an open tag without a matching close tag | |
| _ = TemplateParser.parse(t"<div x={a}/>") | |
| def test_non_void_with_interpolated_end_attribute_no_whitespace_closed(): | |
| a = "wow" | |
| node = TemplateParser.parse(t"<div x={a}/></div>") | |
| assert node == TElement( | |
| "div", | |
| attrs=( | |
| TTemplatedAttribute( | |
| name="x", value_ref=TemplateRef(strings=("", "/"), i_indexes=(0,)) | |
| ), | |
| ), | |
| ) | |
| def test_non_void_with_quoted_end_attribute_no_whitespace(): | |
| with pytest.raises(ValueError): | |
| # Raises because this is not a void tag, so this syntax is illegal | |
| _ = TemplateParser.parse(t'<div x="a"/>') | |
| def test_non_void_with_boolean_end_attribute_no_whitespace(): | |
| with pytest.raises(ValueError): | |
| # Raises because this is not a void tag, so this syntax is illegal | |
| _ = TemplateParser.parse(t"<div x/>") | |
| def test_non_void_with_spread_attribute_no_whitespace(): | |
| attrs = {"x": "a"} | |
| with pytest.raises(ValueError): | |
| # Raises because this is not a void tag, so this syntax is illegal | |
| _ = TemplateParser.parse(t"<div {attrs}/>") | |
| def test_non_void_with_interpolated_end_attribute_whitespace(): | |
| a = "wow" | |
| with pytest.raises(ValueError): | |
| # Raises because this is not a void tag, so this syntax is illegal | |
| _ = TemplateParser.parse(t"<div x={a} />") | |
| def test_non_void_with_multiple_attributes_no_whitespace_closed(): | |
| node = TemplateParser.parse(t"<div y=b x=a/></div>") | |
| assert node == TElement( | |
| "div", | |
| attrs=( | |
| TLiteralAttribute(name="y", value="b"), | |
| TLiteralAttribute(name="x", value="a/"), | |
| ), | |
| ) | |
| def test_component_with_unquoted_end_attribute_no_whitespace(): | |
| def Component(x: str): | |
| pass | |
| with pytest.raises(ValueError): | |
| # Raises because this is an open tag without a matching close tag | |
| _ = TemplateParser.parse(t"<{Component} x=a/>") | |
| def test_component_with_unquoted_end_attribute_whitespace(): | |
| def Component(x: str): | |
| pass | |
| node = TemplateParser.parse(t"<{Component} x=a />") | |
| assert node == TComponent( | |
| start_i_index=0, attrs=(TLiteralAttribute(name="x", value="a"),) | |
| ) | |
| def test_component_with_unquoted_end_attribute_no_whitespace_closed(): | |
| def Component(x: str): | |
| pass | |
| node = TemplateParser.parse(t"<{Component} x=a/></{Component}>") | |
| assert node == TComponent( | |
| start_i_index=0, | |
| end_i_index=1, | |
| attrs=(TLiteralAttribute(name="x", value="a/"),), | |
| ) | |
| def test_component_with_interpolated_end_attribute_no_whitespace(): | |
| def Component(x: str): | |
| pass | |
| a = "wow" | |
| with pytest.raises(ValueError): | |
| # Raises because this is an open tag without a corresponding close | |
| _ = TemplateParser.parse(t"<{Component} x={a}/>") | |
| def test_component_with_interpolated_end_attribute_whitespace(): | |
| def Component(x: str): | |
| pass | |
| a = "wow" | |
| node = TemplateParser.parse(t"<{Component} x={a} />") | |
| assert node == TComponent( | |
| start_i_index=0, attrs=(TInterpolatedAttribute(name="x", value_i_index=1),) | |
| ) | |
| def test_component_with_interpolated_end_attribute_no_whitespace_closed(): | |
| def Component(x: str): | |
| pass | |
| a = "wow" | |
| node = TemplateParser.parse(t"<{Component} x={a}/></{Component}>") | |
| assert node == TComponent( | |
| start_i_index=0, | |
| end_i_index=2, | |
| attrs=( | |
| TTemplatedAttribute( | |
| name="x", value_ref=TemplateRef(strings=("", "/"), i_indexes=(1,)) | |
| ), | |
| ), | |
| ) | |
| def test_component_with_no_attributes_no_whitespace(): | |
| def Component(): | |
| pass | |
| node = TemplateParser.parse(t"<{Component}/>") | |
| assert node == TComponent(start_i_index=0) | |
| def test_component_with_no_attributes_whitespace(): | |
| def Component(): | |
| pass | |
| node = TemplateParser.parse(t"<{Component} />") | |
| assert node == TComponent(start_i_index=0) | |
| def test_component_with_quoted_end_attribute_no_whitespace(): | |
| def Component(x: str): | |
| pass | |
| node = TemplateParser.parse(t'<{Component} x="a"/>') | |
| assert node == TComponent( | |
| start_i_index=0, attrs=(TLiteralAttribute(name="x", value="a"),) | |
| ) | |
| def test_component_with_boolean_end_attribute_no_whitespace(): | |
| def Component(x: bool = False): | |
| pass | |
| node = TemplateParser.parse(t"<{Component} x/>") | |
| assert node == TComponent( | |
| start_i_index=0, attrs=(TLiteralAttribute(name="x", value=None),) | |
| ) | |
| def test_component_with_spread_end_attribute_no_whitespace(): | |
| def Component(**kwargs): | |
| pass | |
| attrs = {"x": "a"} | |
| node = TemplateParser.parse(t"<{Component} {attrs}/>") | |
| assert node == TComponent(start_i_index=0, attrs=(TSpreadAttribute(i_index=1),)) | |
| # | |
| # SVG Behavior | |
| # | |
| def test_svg_with_no_attributes_no_whitespace(): | |
| node = TemplateParser.parse(t"<svg><rect/></svg>") | |
| assert node == TElement("svg", children=(TElement("rect"),)) | |
| def test_svg_with_no_attributes_whitespace(): | |
| node = TemplateParser.parse(t"<svg><rect /></svg>") | |
| assert node == TElement("svg", children=(TElement("rect"),)) | |
| def test_svg_with_unquoted_end_attribute_no_whitespace(): | |
| with pytest.raises(ValueError): | |
| # Raises because the unquoted value absorbs the "/", leaving <rect> open | |
| _ = TemplateParser.parse(t"<svg><rect x=a/></svg>") | |
| def test_svg_with_unquoted_end_attribute_whitespace(): | |
| node = TemplateParser.parse(t"<svg><rect x=a /></svg>") | |
| assert node == TElement( | |
| "svg", | |
| children=(TElement("rect", attrs=(TLiteralAttribute(name="x", value="a"),)),), | |
| ) | |
| def test_svg_with_unquoted_end_attribute_no_whitespace_closed(): | |
| node = TemplateParser.parse(t"<svg><rect x=a/></rect></svg>") | |
| assert node == TElement( | |
| "svg", | |
| children=(TElement("rect", attrs=(TLiteralAttribute(name="x", value="a/"),)),), | |
| ) | |
| def test_svg_with_quoted_end_attribute_no_whitespace(): | |
| node = TemplateParser.parse(t'<svg><rect x="a"/></svg>') | |
| assert node == TElement( | |
| "svg", | |
| children=(TElement("rect", attrs=(TLiteralAttribute(name="x", value="a"),)),), | |
| ) | |
| def test_svg_with_boolean_end_attribute_no_whitespace(): | |
| node = TemplateParser.parse(t"<svg><rect x/></svg>") | |
| assert node == TElement( | |
| "svg", | |
| children=(TElement("rect", attrs=(TLiteralAttribute(name="x", value=None),)),), | |
| ) | |
| def test_svg_with_spread_end_attribute_no_whitespace(): | |
| attrs = {"x": "a"} | |
| node = TemplateParser.parse(t"<svg><rect {attrs}/></svg>") | |
| assert node == TElement( | |
| "svg", | |
| children=(TElement("rect", attrs=(TSpreadAttribute(i_index=0),)),), | |
| ) | |
| def test_svg_with_interpolated_end_attribute_no_whitespace(): | |
| a = "wow" | |
| with pytest.raises(ValueError): | |
| # Raises because the interpolated value absorbs the "/", leaving <rect> open | |
| _ = TemplateParser.parse(t"<svg><rect x={a}/></svg>") | |
| def test_svg_with_interpolated_end_attribute_whitespace(): | |
| a = "wow" | |
| node = TemplateParser.parse(t"<svg><rect x={a} /></svg>") | |
| assert node == TElement( | |
| "svg", | |
| children=( | |
| TElement( | |
| "rect", | |
| attrs=(TInterpolatedAttribute(name="x", value_i_index=0),), | |
| ), | |
| ), | |
| ) | |
| def test_svg_with_interpolated_end_attribute_no_whitespace_closed(): | |
| a = "wow" | |
| node = TemplateParser.parse(t"<svg><rect x={a}/></rect></svg>") | |
| assert node == TElement( | |
| "svg", | |
| children=( | |
| TElement( | |
| "rect", | |
| attrs=( | |
| TTemplatedAttribute( | |
| name="x", | |
| value_ref=TemplateRef(strings=("", "/"), i_indexes=(0,)), | |
| ), | |
| ), | |
| ), | |
| ), | |
| ) | |
| def test_svg_element_at_html_root_no_whitespace(): | |
| with pytest.raises(ValueError): | |
| # Raises because at the HTML root <rect> is an unknown, non-void element | |
| _ = TemplateParser.parse(t"<rect/>") | |
| def test_svg_element_at_svg_root_no_whitespace(): | |
| # SPECULATIVE: for this to make sense, I assume `TElement` needs to hold on | |
| # to a namespace too (`TElement.ns`, with a default of `Namespace.HTML`) | |
| # | |
| # We support Namespace.HTML, Namespace.SVG, and Namespace.MATH, I guess, | |
| # since those are the only true three? | |
| # | |
| # And how are component return values (say, `t"<rect/>"`) interpreted? | |
| # I suppose components don't declare a namespace; they inherit their | |
| # call site's? ... on the *render* side, we are context-aware for | |
| # component return values; i suppose we'd need this to be true on the | |
| # parse side, too, that is `_process_template(...)` would need to | |
| # take a context. | |
| node = TemplateParser.parse(t"<rect/>", namespace=Namespace.SVG) | |
| assert node == TElement("rect") | |
| # To consider: foreignObject, back to HTML. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment