@@ -645,3 +645,126 @@ def test_conditional_comment_ignores_whitespace_inside_nested_block_elements():
645645 single_line = '<div><!--[if mso | IE]><table><tr><td><v:image src="test.jpg" /><![endif]--></div>' # noqa: E501
646646 result = compare_html (multi_line , single_line )
647647 assert result .is_equal
648+
649+
650+
651+ class TestSelfClosingTagDetection :
652+ def test_detects_vml_rect_self_closing_vs_opening_tag (self ):
653+ # v:rect is a VML element where self-closing syntax matters for Outlook.
654+ result = compare_html (
655+ '<div><v:rect style="width:100px" /></div>' ,
656+ '<div><v:rect style="width:100px" ></v:rect></div>' ,
657+ )
658+ assert not result .is_equal
659+ diff , = [d for d in result .differences if d .type == DifferenceType .SELF_CLOSING_MISMATCH ]
660+ assert 'v:rect' in diff .path
661+
662+ def test_detects_vml_fill_self_closing_difference (self ):
663+ result = compare_html (
664+ '<v:fill color="red" />' ,
665+ '<v:fill color="red" ></v:fill>' ,
666+ )
667+ assert not result .is_equal
668+
669+ def test_same_vml_self_closing_elements_are_equal (self ):
670+ html = '<v:rect xmlns:v="urn:schemas-microsoft-com:vml" style="width:100px" />'
671+ assert compare_html (html , html ).is_equal
672+
673+ def test_same_vml_non_self_closing_elements_are_equal (self ):
674+ html = '<v:rect style="width:100px"></v:rect>'
675+ assert compare_html (html , html ).is_equal
676+
677+ def test_detects_script_self_closing_difference (self ):
678+ # <script /> behaves differently from <script></script> in browsers.
679+ result = compare_html (
680+ '<script src="app.js" />' ,
681+ '<script src="app.js"></script>' ,
682+ )
683+ assert not result .is_equal
684+
685+ def test_detects_style_self_closing_difference (self ):
686+ # <style /> behaves differently from <style></style> in browsers.
687+ result = compare_html (
688+ '<style type="text/css" />' ,
689+ '<style type="text/css"></style>' ,
690+ )
691+ assert not result .is_equal
692+
693+ def test_ignores_html5_void_element_self_closing_br (self ):
694+ assert compare_html ('<br>' , '<br/>' ).is_equal
695+ assert compare_html ('<br>' , '<br />' ).is_equal
696+ assert compare_html ('<br/>' , '<br />' ).is_equal
697+
698+ def test_ignores_html5_void_element_self_closing_img (self ):
699+ assert compare_html (
700+ '<img src="test.jpg" alt="">' ,
701+ '<img src="test.jpg" alt="" />' ,
702+ ).is_equal
703+
704+ def test_ignores_html5_void_element_self_closing_input (self ):
705+ assert compare_html (
706+ '<input type="text" name="foo">' ,
707+ '<input type="text" name="foo" />' ,
708+ ).is_equal
709+
710+ def test_ignores_html5_void_element_self_closing_meta (self ):
711+ assert compare_html (
712+ '<meta charset="utf-8">' ,
713+ '<meta charset="utf-8" />' ,
714+ ).is_equal
715+
716+ def test_ignores_regular_div_self_closing (self ):
717+ # Regular HTML elements like div - self-closing doesn't matter semantically.
718+ # Note: both become <div></div> after parsing, self-closing div is invalid HTML
719+ assert compare_html ('<div />' , '<div></div>' ).is_equal
720+
721+ def test_vml_in_conditional_comment (self ):
722+ # VML self-closing detection works also inside conditional comments.
723+ result = compare_html (
724+ '<div><!--[if mso]><v:rect style="width:100px" /><![endif]--></div>' ,
725+ '<div><!--[if mso]><v:rect style="width:100px" ></v:rect><![endif]--></div>' ,
726+ )
727+ assert not result .is_equal
728+
729+ def test_multiple_vml_elements_with_mixed_self_closing (self ):
730+ # Each VML element's self-closing status is checked independently.
731+ html = '<v:rect /><v:fill /><v:stroke />'
732+ assert compare_html (html , html ).is_equal
733+
734+ # different self-closing on v:fill
735+ result = compare_html (
736+ '<v:rect /><v:fill /><v:stroke />' ,
737+ '<v:rect /><v:fill ></v:fill><v:stroke />' ,
738+ )
739+ assert not result .is_equal
740+
741+ def test_detects_textarea_self_closing_difference (self ):
742+ # <textarea /> is invalid and may cause rendering issues.
743+ result = compare_html (
744+ '<textarea name="comment" />' ,
745+ '<textarea name="comment"></textarea>' ,
746+ )
747+ assert not result .is_equal
748+
749+ def test_detects_iframe_self_closing_difference (self ):
750+ result = compare_html (
751+ '<iframe src="page.html" />' ,
752+ '<iframe src="page.html"></iframe>' ,
753+ )
754+ assert not result .is_equal
755+
756+ def test_unknown_vml_namespace_element (self ):
757+ # Any v: prefixed element should have self-closing checked.
758+ result = compare_html (
759+ '<v:customshape fill="red" />' ,
760+ '<v:customshape fill="red"></v:customshape>' ,
761+ )
762+ assert not result .is_equal
763+
764+ def test_office_namespace_element (self ):
765+ # o: prefixed elements (Office namespace) should have self-closing checked.
766+ result = compare_html (
767+ '<o:lock aspectratio="t" />' ,
768+ '<o:lock aspectratio="t"></o:lock>' ,
769+ )
770+ assert not result .is_equal
0 commit comments