@@ -618,4 +618,155 @@ public function test_table_templates_not_yet_supported() {
618618 HTML;
619619 $ this ->assertEqualHTML ( $ expected , $ result );
620620 }
621+
622+ /**
623+ * Verifies that attributes are replaced in atomic elements (SCRIPT, STYLE, TITLE).
624+ *
625+ * These elements have special parsing rules that skip their content,
626+ * but attributes should still be processed normally.
627+ *
628+ * @ticket 60229
629+ *
630+ * @dataProvider data_atomic_element_attributes
631+ *
632+ * @covers ::from
633+ * @covers ::render
634+ * @covers ::sprintf
635+ */
636+ public function test_atomic_element_attributes_are_replaced ( string $ template_string , array $ replacements , string $ expected ) {
637+ $ t = T::from ( $ template_string );
638+ $ result = $ t ->render ( $ replacements );
639+ $ this ->assertSame ( $ result , T::sprintf ( $ template_string , $ replacements ) );
640+ $ this ->assertSame ( $ expected , $ result );
641+ }
642+
643+ public static function data_atomic_element_attributes () {
644+ return array (
645+ 'SCRIPT element attributes ' => array (
646+ '<script src="</%src>">console.log("hi")</script> ' ,
647+ array ( 'src ' => '/js/app.js ' ),
648+ '<script src="/js/app.js">console.log("hi")</script> ' ,
649+ ),
650+
651+ 'STYLE element attributes ' => array (
652+ '<style media="</%media>">.foo { color: red; }</style> ' ,
653+ array ( 'media ' => 'screen ' ),
654+ '<style media="screen">.foo { color: red; }</style> ' ,
655+ ),
656+
657+ 'TITLE element attributes ' => array (
658+ '<title lang="</%lang>">Page Title</title> ' ,
659+ array ( 'lang ' => 'en ' ),
660+ '<title lang="en">Page Title</title> ' ,
661+ ),
662+
663+ 'TEXTAREA element attributes ' => array (
664+ '<textarea name="</%name>">Some content</textarea> ' ,
665+ array ( 'name ' => 'my-textarea ' ),
666+ '<textarea name="my-textarea">Some content</textarea> ' ,
667+ ),
668+ );
669+ }
670+
671+ /**
672+ * Verifies content placeholder behavior in elements with special parsing.
673+ *
674+ * - RAWTEXT elements (SCRIPT, STYLE): Content is skipped, placeholders preserved literally.
675+ * - RCDATA elements (TITLE, TEXTAREA): Content is processed but placeholders are not
676+ * recognized - they're treated as literal text and HTML-escaped.
677+ *
678+ * @ticket 60229
679+ *
680+ * @dataProvider data_atomic_element_content_placeholders
681+ *
682+ * @covers ::from
683+ * @covers ::render
684+ * @covers ::sprintf
685+ */
686+ public function test_special_element_content_placeholder_behavior ( string $ template_string , array $ replacements , string $ expected ) {
687+ $ t = T::from ( $ template_string );
688+ $ result = $ t ->render ( $ replacements );
689+ $ this ->assertSame ( $ result , T::sprintf ( $ template_string , $ replacements ) );
690+ $ this ->assertSame ( $ expected , $ result );
691+ }
692+
693+ public static function data_atomic_element_content_placeholders () {
694+ return array (
695+ // RAWTEXT elements (SCRIPT, STYLE): Content is truly skipped, placeholders preserved literally.
696+ 'SCRIPT content placeholder ignored ' => array (
697+ '<script>var x = "</%name>";</script> ' ,
698+ array ( 'name ' => 'SHOULD NOT APPEAR ' ),
699+ '<script>var x = "</%name>";</script> ' ,
700+ ),
701+
702+ 'STYLE content placeholder ignored ' => array (
703+ '<style>.foo { content: "</%content>"; }</style> ' ,
704+ array ( 'content ' => 'SHOULD NOT APPEAR ' ),
705+ '<style>.foo { content: "</%content>"; }</style> ' ,
706+ ),
707+
708+ // RCDATA elements (TITLE, TEXTAREA): Content is processed but placeholder
709+ // patterns are not recognized - they're treated as literal text and escaped.
710+ 'TITLE content placeholder not recognized ' => array (
711+ '<title>Hello </%name></title> ' ,
712+ array ( 'name ' => 'SHOULD NOT APPEAR ' ),
713+ '<title>Hello </%name></title> ' ,
714+ ),
715+
716+ 'TEXTAREA content placeholder not recognized ' => array (
717+ '<textarea></%placeholder></textarea> ' ,
718+ array ( 'placeholder ' => 'SHOULD NOT APPEAR ' ),
719+ '<textarea></%placeholder></textarea> ' ,
720+ ),
721+ );
722+ }
723+
724+ /**
725+ * Verifies leading newline behavior in PRE elements.
726+ *
727+ * HTML5 specifies that a single leading newline immediately after the
728+ * <pre> start tag is ignored. This test documents the template behavior.
729+ *
730+ * @ticket 60229
731+ *
732+ * @dataProvider data_pre_element_leading_newline
733+ *
734+ * @covers ::from
735+ * @covers ::render
736+ * @covers ::sprintf
737+ */
738+ public function test_pre_element_leading_newline_behavior ( string $ template_string , array $ replacements , string $ expected ) {
739+ $ t = T::from ( $ template_string );
740+ $ result = $ t ->render ( $ replacements );
741+ $ this ->assertSame ( $ result , T::sprintf ( $ template_string , $ replacements ) );
742+ $ this ->assertSame ( $ expected , $ result );
743+ }
744+
745+ public static function data_pre_element_leading_newline () {
746+ return array (
747+ 'PRE without newline ' => array (
748+ "<pre></%code></pre> " ,
749+ array ( 'code ' => "line1 \nline2 " ),
750+ "<pre>line1 \nline2</pre> " ,
751+ ),
752+
753+ 'PRE with newline ' => array (
754+ "<pre> \n</%code></pre> " ,
755+ array ( 'code ' => "line1 \nline2 " ),
756+ "<pre>line1 \nline2</pre> " ,
757+ ),
758+
759+ 'PRE with newline in replacement ' => array (
760+ "<pre> \n</%code></pre> " ,
761+ array ( 'code ' => "line1 \nline2 " ),
762+ "<pre>line1 \nline2</pre> " ,
763+ ),
764+
765+ 'PRE with newline and newline in replacement ' => array (
766+ "<pre> \n</%code></pre> " ,
767+ array ( 'code ' => "\nline1 \nline2 " ),
768+ "<pre> \nline1 \nline2</pre> " ,
769+ ),
770+ );
771+ }
621772}
0 commit comments