From 063148bfd6a68d61750f3ebbb7c060bccb313525 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 18 Aug 2026 05:51:18 +0000 Subject: [PATCH 1/2] Refactor OutputRules tests into shared base plus runtime-specific subclasses Co-authored-by: goetas <776743+goetas@users.noreply.github.com> --- .../Serializer/AbstractOutputRulesTest.php | 607 ++++++++++++++++++ .../Serializer/OutputRulesNewDomTest.php | 91 +++ test/HTML5/Serializer/OutputRulesTest.php | 591 +---------------- 3 files changed, 703 insertions(+), 586 deletions(-) create mode 100644 test/HTML5/Serializer/AbstractOutputRulesTest.php create mode 100644 test/HTML5/Serializer/OutputRulesNewDomTest.php diff --git a/test/HTML5/Serializer/AbstractOutputRulesTest.php b/test/HTML5/Serializer/AbstractOutputRulesTest.php new file mode 100644 index 0000000..c7f5c97 --- /dev/null +++ b/test/HTML5/Serializer/AbstractOutputRulesTest.php @@ -0,0 +1,607 @@ + + + + + Test + + +

This is a test.

+ + '; + + /** + * @var HTML5 + */ + protected $html5; + + /** + * @before + */ + public function before() + { + $this->html5 = $this->getInstance(); + } + + abstract protected function loadHTML($html); + + protected function createCommentNode($dom, $value) + { + return new \DOMComment($value); + } + + protected function createTextNode($dom, $value) + { + return new \DOMText($value); + } + + /** + * Using reflection we make a protected method accessible for testing. + * + * @param string $name + * The name of the method on the Traverser class to test + * + * @return \ReflectionMethod for the specified method + */ + public function getProtectedMethod($name) + { + $class = new \ReflectionClass('\Masterminds\HTML5\Serializer\OutputRules'); + $method = $class->getMethod($name); + $method->setAccessible(true); + + return $method; + } + + public function getTraverserProtectedProperty($name) + { + $class = new \ReflectionClass('\Masterminds\HTML5\Serializer\Traverser'); + $property = $class->getProperty($name); + $property->setAccessible(true); + + return $property; + } + + public function getOutputRules($options = array()) + { + $options = $options + $this->html5->getOptions(); + $stream = fopen('php://temp', 'w'); + $dom = $this->loadHTML($this->markup); + $r = new OutputRules($stream, $options); + $t = new Traverser($dom, $stream, $r, $options); + + return array( + $r, + $stream, + ); + } + + public function testDocument() + { + $dom = $this->loadHTML('foo'); + + $stream = fopen('php://temp', 'w'); + $r = new OutputRules($stream, $this->html5->getOptions()); + $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); + + $r->document($dom); + $expected = '' . PHP_EOL . 'foo' . PHP_EOL; + $this->assertEquals($expected, stream_get_contents($stream, -1, 0)); + } + + public function testEmptyDocument() + { + $dom = $this->loadHTML(''); + + $stream = fopen('php://temp', 'w'); + $r = new OutputRules($stream, $this->html5->getOptions()); + $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); + + $r->document($dom); + $expected = '' . PHP_EOL; + $this->assertEquals($expected, stream_get_contents($stream, -1, 0)); + } + + public function testDoctype() + { + $dom = $this->loadHTML('foo'); + + $stream = fopen('php://temp', 'w'); + $r = new OutputRules($stream, $this->html5->getOptions()); + $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); + + $m = $this->getProtectedMethod('doctype'); + $m->invoke($r, 'foo'); + $this->assertEquals('' . PHP_EOL, stream_get_contents($stream, -1, 0)); + } + + public function testElement() + { + $dom = $this->loadHTML( + ' + + +
foo bar baz
+ + + + + + + '); + + $stream = fopen('php://temp', 'w'); + $r = new OutputRules($stream, $this->html5->getOptions()); + $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); + + $list = $dom->getElementsByTagName('div'); + $r->element($list->item(0)); + $this->assertEquals('
foo bar baz
', stream_get_contents($stream, -1, 0)); + } + + public function testElementWithScript() + { + $dom = $this->loadHTML( + ' + + + + + +
foo bar baz
+ + '); + + $stream = fopen('php://temp', 'w'); + $r = new OutputRules($stream, $this->html5->getOptions()); + $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); + + $script = $dom->getElementsByTagName('script'); + $r->element($script->item(0)); + $this->assertEquals( + '', stream_get_contents($stream, -1, 0)); + } + + public function testElementWithStyle() + { + $dom = $this->loadHTML( + ' + + + + + +
foo bar baz
+ + '); + + $stream = fopen('php://temp', 'w'); + $r = new OutputRules($stream, $this->html5->getOptions()); + $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); + + $style = $dom->getElementsByTagName('style'); + $r->element($style->item(0)); + $this->assertEquals('', stream_get_contents($stream, -1, 0)); + } + + public function testOpenTag() + { + $dom = $this->loadHTML(' + + +
foo bar baz
+ + '); + + $stream = fopen('php://temp', 'w'); + $r = new OutputRules($stream, $this->html5->getOptions()); + $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); + + $list = $dom->getElementsByTagName('div'); + $m = $this->getProtectedMethod('openTag'); + $m->invoke($r, $list->item(0)); + $this->assertEquals('
', stream_get_contents($stream, -1, 0)); + } + + public function testComment() + { + $dom = $this->loadHTML(' + + +
+ + '); + + $stream = fopen('php://temp', 'w'); + $r = new OutputRules($stream, $this->html5->getOptions()); + $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); + + $list = $dom->getElementsByTagName('div'); + $r->comment($list->item(0)->childNodes->item(0)); + $this->assertEquals('', stream_get_contents($stream, -1, 0)); + + $dom = $this->loadHTML(' + + +
+ + '); + $dom->getElementById('foo')->appendChild($this->createCommentNode($dom, ' --> Foo -->')); + + $stream = fopen('php://temp', 'w'); + $r = new OutputRules($stream, $this->html5->getOptions()); + $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); + + $list = $dom->getElementsByTagName('div'); + $r->comment($list->item(0)->childNodes->item(0)); + + // Could not find more definitive guidelines on what this should be. Went with + // what the HTML5 spec says and what \DOMDocument::saveXML() produces. + $this->assertEquals(' --> Foo -->-->', stream_get_contents($stream, -1, 0)); + } + + public function testText() + { + $dom = $this->loadHTML(' + + + + + '); + + $stream = fopen('php://temp', 'w'); + $r = new OutputRules($stream, $this->html5->getOptions()); + $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); + + $list = $dom->getElementsByTagName('script'); + $r->text($list->item(0)->childNodes->item(0)); + $this->assertEquals('baz();', stream_get_contents($stream, -1, 0)); + + $dom = $this->loadHTML(' + + + '); + $foo = $dom->getElementById('foo'); + $foo->appendChild($this->createTextNode($dom, '')); + + $stream = fopen('php://temp', 'w'); + $r = new OutputRules($stream, $this->html5->getOptions()); + $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); + + $r->text($foo->firstChild); + $this->assertEquals('<script>alert("hi");</script>', stream_get_contents($stream, -1, 0)); + } + + public function testNl() + { + list($o, $s) = $this->getOutputRules(); + + $m = $this->getProtectedMethod('nl'); + $m->invoke($o); + $this->assertEquals(PHP_EOL, stream_get_contents($s, -1, 0)); + } + + public function testWr() + { + list($o, $s) = $this->getOutputRules(); + + $m = $this->getProtectedMethod('wr'); + $m->invoke($o, 'foo'); + $this->assertEquals('foo', stream_get_contents($s, -1, 0)); + } + + public function testWrWithNullNodeValue() + { + // Namespace nodes with an empty URI (xmlns:w="") can expose a null nodeValue; verify no error on serialization. + $this->html5 = $this->getInstance(array('xmlNamespaces' => true)); + $input = ''; + $dom = $this->loadHTML($input); + $result = $this->html5->saveHTML($dom); + $this->assertTrue(false !== strpos($result, 'xmlns:w=""')); + } + + public function getEncData() + { + return array( + array( + false, + '&\'<>"', + '&\'<>"', + '&'<>"', + ), + array( + false, + 'This + is. a < test', + 'This + is. a < test', + 'This + is. a < test', + ), + array( + false, + '.+#', + '.+#', + '.+#', + ), + + array( + true, + '.+#\'', + '.+#\'', + '.+#'', + ), + array( + true, + '&".<', + '&".<', + '&".<', + ), + array( + true, + '&\'<>"', + '&\'<>"', + '&'<>"', + ), + array( + true, + "\xc2\xa0\"'", + ' "\'', + ' "'', + ), + ); + } + + /** + * Test basic encoding of text. + * + * @dataProvider getEncData + */ + public function testEnc($isAttribute, $test, $expected, $expectedEncoded) + { + list($o, $s) = $this->getOutputRules(); + $m = $this->getProtectedMethod('enc'); + + $this->assertEquals($expected, $m->invoke($o, $test, $isAttribute)); + + list($o, $s) = $this->getOutputRules(array( + 'encode_entities' => true, + )); + $m = $this->getProtectedMethod('enc'); + $this->assertEquals($expectedEncoded, $m->invoke($o, $test, $isAttribute)); + } + + /** + * Test basic encoding of text. + * + * @dataProvider getEncData + */ + public function testEscape($isAttribute, $test, $expected, $expectedEncoded) + { + list($o, $s) = $this->getOutputRules(); + $m = $this->getProtectedMethod('escape'); + + $this->assertEquals($expected, $m->invoke($o, $test, $isAttribute)); + } + + public function booleanAttributes() + { + return array( + array(''), + array(''), + array(''), + array(''), + array(''), + array(''), + array('
'), + array(''), + array('
'), + array(''), + ); + } + + /** + * @dataProvider booleanAttributes + */ + public function testBooleanAttrs($html) + { + $dom = $this->loadHTML('' . $html . ''); + + $stream = fopen('php://temp', 'w'); + $r = new OutputRules($stream, $this->html5->getOptions()); + $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); + + $node = $dom->getElementsByTagName('body')->item(0)->firstChild; + + $m = $this->getProtectedMethod('attrs'); + $m->invoke($r, $node); + + $content = stream_get_contents($stream, -1, 0); + + $html = preg_replace('~<[a-z]+(.*)>~', '\1', $html); + $html = preg_replace('~<[a-z]+(.*)/?>~', '\1', $html); + + $this->assertEquals($content, $html); + } + + public function testAttrs() + { + $dom = $this->loadHTML(' + + +
foo bar baz
+ + '); + + $stream = fopen('php://temp', 'w'); + $r = new OutputRules($stream, $this->html5->getOptions()); + $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); + + $list = $dom->getElementsByTagName('div'); + + $m = $this->getProtectedMethod('attrs'); + $m->invoke($r, $list->item(0)); + + $content = stream_get_contents($stream, -1, 0); + $this->assertEquals(' id="foo" class="bar baz"', $content); + } + + public function testSvg() + { + $dom = $this->loadHTML( + ' + + +
foo bar baz
+ + + + + + + + + + '); + + $stream = fopen('php://temp', 'w'); + $r = new OutputRules($stream, $this->html5->getOptions()); + $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); + + $list = $dom->getElementsByTagName('svg'); + $r->element($list->item(0)); + $contents = stream_get_contents($stream, -1, 0); + $this->assertMatchesRegularExpression('||', $contents); + $this->assertMatchesRegularExpression('||', $contents); + $this->assertMatchesRegularExpression('||', $contents); + } + + public function testMath() + { + $dom = $this->loadHTML( + ' + + +
foo bar baz
+ + x + + ± + + y + + + '); + + $stream = fopen('php://temp', 'w'); + $r = new OutputRules($stream, $this->html5->getOptions()); + $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); + + $list = $dom->getElementsByTagName('math'); + $r->element($list->item(0)); + $content = stream_get_contents($stream, -1, 0); + $this->assertMatchesRegularExpression('||', $content); + $this->assertMatchesRegularExpression('||', $content); + } + + public function testAddressTag() + { + $dom = $this->loadHTML( + ' + + +
+ Dave Raggett, + Arnaud Le Hors, + contact persons for the W3C HTML Activity +
+ + '); + + $stream = fopen('php://temp', 'w'); + $r = new OutputRules($stream, $this->html5->getOptions()); + $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); + + $list = $dom->getElementsByTagName('address'); + $r->element($list->item(0)); + $contents = stream_get_contents($stream, -1, 0); + + $this->assertMatchesRegularExpression('|
|', $contents); + $this->assertMatchesRegularExpression('|Dave Raggett,|', $contents); + $this->assertMatchesRegularExpression('|Arnaud Le Hors,|', $contents); + $this->assertMatchesRegularExpression('|contact persons for the W3C HTML Activity|', $contents); + $this->assertMatchesRegularExpression('|
|', $contents); + } + + /** + * Ensure direct DOM manipulation doesn't break TEXT_RAW elements (iframe, script, etc...). + */ + public function testHandlingInvalidRawContent() + { + $dom = $this->loadHTML( + ' + + + + +'); + + $badNode = $dom->createElement('p', 'Bar'); + + // modify the content of the TEXT_RAW element: ')); + } + + public function testSvgAndMathElementsWithoutChildNodesAreHandledAsVoidTags() + { + $dom = $this->loadHTML( + ' + + + + + +'); + + $contents = $this->html5->saveHTML($dom); + + self::assertMatchesRegularExpression('|^\h*$|m', $contents); + self::assertMatchesRegularExpression('|^\h*$|m', $contents); + } +} diff --git a/test/HTML5/Serializer/OutputRulesNewDomTest.php b/test/HTML5/Serializer/OutputRulesNewDomTest.php new file mode 100644 index 0000000..5cca9ad --- /dev/null +++ b/test/HTML5/Serializer/OutputRulesNewDomTest.php @@ -0,0 +1,91 @@ +createComment($value); + } + + protected function createTextNode($dom, $value) + { + return $dom->createTextNode($value); + } + + public function testSerializeWithNamespaces() + { + $this->html5 = $this->getInstance(array( + 'xmlNamespaces' => true, + )); + + $source = ' + + + + xy + + svg + +
+ + y + '; + + $dom = $this->loadHTML($source); + + $stream = fopen('php://temp', 'w'); + $r = new OutputRules($stream, $this->html5->getOptions()); + $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); + + $t->walk(); + $rendered = stream_get_contents($stream, -1, 0); + + $clear = function ($s) { + return trim(preg_replace('/[\s]+/', ' ', $s)); + }; + + $this->assertEquals($clear($source), $clear($rendered)); + } + + public function testProcessorInstruction() + { + $doc = HTMLDocument::createEmpty(); + $dom = $doc->createProcessingInstruction('foo', 'bar '); + + $stream = fopen('php://temp', 'w'); + $r = new OutputRules($stream, $this->html5->getOptions()); + $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); + + $r->processorInstruction($dom); + $content = stream_get_contents($stream, -1, 0); + $this->assertMatchesRegularExpression('|<\?foo bar \?>|', $content); + } + + public function testHandlingInvalidRawContent() + { + self::markTestSkipped('Currently \Dom\HTMLElement will break invalid HTML so skip this test.'); + } +} diff --git a/test/HTML5/Serializer/OutputRulesTest.php b/test/HTML5/Serializer/OutputRulesTest.php index bee5978..27ed0a1 100644 --- a/test/HTML5/Serializer/OutputRulesTest.php +++ b/test/HTML5/Serializer/OutputRulesTest.php @@ -2,137 +2,14 @@ namespace Masterminds\HTML5\Tests\Serializer; -use Masterminds\HTML5; use Masterminds\HTML5\Serializer\OutputRules; use Masterminds\HTML5\Serializer\Traverser; -class OutputRulesTest extends \Masterminds\HTML5\Tests\TestCase +class OutputRulesTest extends AbstractOutputRulesTest { - protected $markup = ' - - - - Test - - -

This is a test.

- - '; - - /** - * @var HTML5 - */ - protected $html5; - - /** - * @before - */ - public function before() - { - $this->html5 = $this->getInstance(); - } - - /** - * Using reflection we make a protected method accessible for testing. - * - * @param string $name - * The name of the method on the Traverser class to test - * - * @return \ReflectionMethod for the specified method - */ - public function getProtectedMethod($name) + protected function loadHTML($html) { - $class = new \ReflectionClass('\Masterminds\HTML5\Serializer\OutputRules'); - $method = $class->getMethod($name); - $method->setAccessible(true); - - return $method; - } - - public function getTraverserProtectedProperty($name) - { - $class = new \ReflectionClass('\Masterminds\HTML5\Serializer\Traverser'); - $property = $class->getProperty($name); - $property->setAccessible(true); - - return $property; - } - - public function getOutputRules($options = array()) - { - $options = $options + $this->html5->getOptions(); - $stream = fopen('php://temp', 'w'); - $dom = $this->html5->loadHTML($this->markup); - $r = new OutputRules($stream, $options); - $t = new Traverser($dom, $stream, $r, $options); - - return array( - $r, - $stream, - ); - } - - public function testDocument() - { - $dom = $this->html5->loadHTML('foo'); - - $stream = fopen('php://temp', 'w'); - $r = new OutputRules($stream, $this->html5->getOptions()); - $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); - - $r->document($dom); - $expected = '' . PHP_EOL . 'foo' . PHP_EOL; - $this->assertEquals($expected, stream_get_contents($stream, -1, 0)); - } - - public function testEmptyDocument() - { - $dom = $this->html5->loadHTML(''); - - $stream = fopen('php://temp', 'w'); - $r = new OutputRules($stream, $this->html5->getOptions()); - $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); - - $r->document($dom); - $expected = '' . PHP_EOL; - $this->assertEquals($expected, stream_get_contents($stream, -1, 0)); - } - - public function testDoctype() - { - $dom = $this->html5->loadHTML('foo'); - - $stream = fopen('php://temp', 'w'); - $r = new OutputRules($stream, $this->html5->getOptions()); - $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); - - $m = $this->getProtectedMethod('doctype'); - $m->invoke($r, 'foo'); - $this->assertEquals('' . PHP_EOL, stream_get_contents($stream, -1, 0)); - } - - public function testElement() - { - $dom = $this->html5->loadHTML( - ' - - -
foo bar baz
- - - - - - - '); - - $stream = fopen('php://temp', 'w'); - $r = new OutputRules($stream, $this->html5->getOptions()); - $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); - - $list = $dom->getElementsByTagName('div'); - $r->element($list->item(0)); - $this->assertEquals('
foo bar baz
', stream_get_contents($stream, -1, 0)); + return $this->html5->loadHTML($html); } public function testSerializeWithNamespaces() @@ -174,95 +51,9 @@ public function testSerializeWithNamespaces() $this->assertEquals($clear($source), $clear($rendered)); } - public function testElementWithScript() - { - $dom = $this->html5->loadHTML( - ' - - - - - -
foo bar baz
- - '); - - $stream = fopen('php://temp', 'w'); - $r = new OutputRules($stream, $this->html5->getOptions()); - $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); - - $script = $dom->getElementsByTagName('script'); - $r->element($script->item(0)); - $this->assertEquals( - '', stream_get_contents($stream, -1, 0)); - } - - public function testElementWithStyle() - { - $dom = $this->html5->loadHTML( - ' - - - - - -
foo bar baz
- - '); - - $stream = fopen('php://temp', 'w'); - $r = new OutputRules($stream, $this->html5->getOptions()); - $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); - - $style = $dom->getElementsByTagName('style'); - $r->element($style->item(0)); - $this->assertEquals('', stream_get_contents($stream, -1, 0)); - } - - public function testOpenTag() - { - $dom = $this->html5->loadHTML(' - - -
foo bar baz
- - '); - - $stream = fopen('php://temp', 'w'); - $r = new OutputRules($stream, $this->html5->getOptions()); - $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); - - $list = $dom->getElementsByTagName('div'); - $m = $this->getProtectedMethod('openTag'); - $m->invoke($r, $list->item(0)); - $this->assertEquals('
', stream_get_contents($stream, -1, 0)); - } - public function testCData() { - $dom = $this->html5->loadHTML(' + $dom = $this->loadHTML('
@@ -277,7 +68,7 @@ public function testCData() $r->cdata($list->item(0)->childNodes->item(0)); $this->assertEquals('', stream_get_contents($stream, -1, 0)); - $dom = $this->html5->loadHTML(' + $dom = $this->loadHTML('
@@ -295,304 +86,6 @@ public function testCData() $this->assertEquals('Foo<[![CDATA test ]]]]>]]>', stream_get_contents($stream, -1, 0)); } - public function testComment() - { - $dom = $this->html5->loadHTML(' - - -
- - '); - - $stream = fopen('php://temp', 'w'); - $r = new OutputRules($stream, $this->html5->getOptions()); - $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); - - $list = $dom->getElementsByTagName('div'); - $r->comment($list->item(0)->childNodes->item(0)); - $this->assertEquals('', stream_get_contents($stream, -1, 0)); - - $dom = $this->html5->loadHTML(' - - -
- - '); - $dom->getElementById('foo')->appendChild(new \DOMComment(' --> Foo -->')); - - $stream = fopen('php://temp', 'w'); - $r = new OutputRules($stream, $this->html5->getOptions()); - $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); - - $list = $dom->getElementsByTagName('div'); - $r->comment($list->item(0)->childNodes->item(0)); - - // Could not find more definitive guidelines on what this should be. Went with - // what the HTML5 spec says and what \DOMDocument::saveXML() produces. - $this->assertEquals(' --> Foo -->-->', stream_get_contents($stream, -1, 0)); - } - - public function testText() - { - $dom = $this->html5->loadHTML(' - - - - - '); - - $stream = fopen('php://temp', 'w'); - $r = new OutputRules($stream, $this->html5->getOptions()); - $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); - - $list = $dom->getElementsByTagName('script'); - $r->text($list->item(0)->childNodes->item(0)); - $this->assertEquals('baz();', stream_get_contents($stream, -1, 0)); - - $dom = $this->html5->loadHTML(' - - - '); - $foo = $dom->getElementById('foo'); - $foo->appendChild(new \DOMText('')); - - $stream = fopen('php://temp', 'w'); - $r = new OutputRules($stream, $this->html5->getOptions()); - $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); - - $r->text($foo->firstChild); - $this->assertEquals('<script>alert("hi");</script>', stream_get_contents($stream, -1, 0)); - } - - public function testNl() - { - list($o, $s) = $this->getOutputRules(); - - $m = $this->getProtectedMethod('nl'); - $m->invoke($o); - $this->assertEquals(PHP_EOL, stream_get_contents($s, -1, 0)); - } - - public function testWr() - { - list($o, $s) = $this->getOutputRules(); - - $m = $this->getProtectedMethod('wr'); - $m->invoke($o, 'foo'); - $this->assertEquals('foo', stream_get_contents($s, -1, 0)); - } - - public function testWrWithNullNodeValue() - { - // Namespace nodes with an empty URI (xmlns:w="") can expose a null nodeValue; verify no error on serialization. - $this->html5 = $this->getInstance(array('xmlNamespaces' => true)); - $input = ''; - $dom = $this->html5->loadHTML($input); - $result = $this->html5->saveHTML($dom); - $this->assertTrue(false !== strpos($result, 'xmlns:w=""')); - } - - public function getEncData() - { - return array( - array( - false, - '&\'<>"', - '&\'<>"', - '&'<>"', - ), - array( - false, - 'This + is. a < test', - 'This + is. a < test', - 'This + is. a < test', - ), - array( - false, - '.+#', - '.+#', - '.+#', - ), - - array( - true, - '.+#\'', - '.+#\'', - '.+#'', - ), - array( - true, - '&".<', - '&".<', - '&".<', - ), - array( - true, - '&\'<>"', - '&\'<>"', - '&'<>"', - ), - array( - true, - "\xc2\xa0\"'", - ' "\'', - ' "'', - ), - ); - } - - /** - * Test basic encoding of text. - * - * @dataProvider getEncData - */ - public function testEnc($isAttribute, $test, $expected, $expectedEncoded) - { - list($o, $s) = $this->getOutputRules(); - $m = $this->getProtectedMethod('enc'); - - $this->assertEquals($expected, $m->invoke($o, $test, $isAttribute)); - - list($o, $s) = $this->getOutputRules(array( - 'encode_entities' => true, - )); - $m = $this->getProtectedMethod('enc'); - $this->assertEquals($expectedEncoded, $m->invoke($o, $test, $isAttribute)); - } - - /** - * Test basic encoding of text. - * - * @dataProvider getEncData - */ - public function testEscape($isAttribute, $test, $expected, $expectedEncoded) - { - list($o, $s) = $this->getOutputRules(); - $m = $this->getProtectedMethod('escape'); - - $this->assertEquals($expected, $m->invoke($o, $test, $isAttribute)); - } - - public function booleanAttributes() - { - return array( - array(''), - array(''), - array(''), - array(''), - array(''), - array(''), - array('
'), - array(''), - array('
'), - array(''), - ); - } - - /** - * @dataProvider booleanAttributes - */ - public function testBooleanAttrs($html) - { - $dom = $this->html5->loadHTML('' . $html . ''); - - $stream = fopen('php://temp', 'w'); - $r = new OutputRules($stream, $this->html5->getOptions()); - $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); - - $node = $dom->getElementsByTagName('body')->item(0)->firstChild; - - $m = $this->getProtectedMethod('attrs'); - $m->invoke($r, $node); - - $content = stream_get_contents($stream, -1, 0); - - $html = preg_replace('~<[a-z]+(.*)>~', '\1', $html); - $html = preg_replace('~<[a-z]+(.*)/?>~', '\1', $html); - - $this->assertEquals($content, $html); - } - - public function testAttrs() - { - $dom = $this->html5->loadHTML(' - - -
foo bar baz
- - '); - - $stream = fopen('php://temp', 'w'); - $r = new OutputRules($stream, $this->html5->getOptions()); - $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); - - $list = $dom->getElementsByTagName('div'); - - $m = $this->getProtectedMethod('attrs'); - $m->invoke($r, $list->item(0)); - - $content = stream_get_contents($stream, -1, 0); - $this->assertEquals(' id="foo" class="bar baz"', $content); - } - - public function testSvg() - { - $dom = $this->html5->loadHTML( - ' - - -
foo bar baz
- - - - - - - - - - '); - - $stream = fopen('php://temp', 'w'); - $r = new OutputRules($stream, $this->html5->getOptions()); - $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); - - $list = $dom->getElementsByTagName('svg'); - $r->element($list->item(0)); - $contents = stream_get_contents($stream, -1, 0); - $this->assertMatchesRegularExpression('||', $contents); - $this->assertMatchesRegularExpression('||', $contents); - $this->assertMatchesRegularExpression('||', $contents); - } - - public function testMath() - { - $dom = $this->html5->loadHTML( - ' - - -
foo bar baz
- - x - - ± - - y - - - '); - - $stream = fopen('php://temp', 'w'); - $r = new OutputRules($stream, $this->html5->getOptions()); - $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); - - $list = $dom->getElementsByTagName('math'); - $r->element($list->item(0)); - $content = stream_get_contents($stream, -1, 0); - $this->assertMatchesRegularExpression('||', $content); - $this->assertMatchesRegularExpression('||', $content); - } - public function testProcessorInstruction() { $dom = $this->html5->loadHTMLFragment(''); @@ -605,78 +98,4 @@ public function testProcessorInstruction() $content = stream_get_contents($stream, -1, 0); $this->assertMatchesRegularExpression('|<\?foo bar \?>|', $content); } - - public function testAddressTag() - { - $dom = $this->html5->loadHTML( - ' - - -
- Dave Raggett, - Arnaud Le Hors, - contact persons for the W3C HTML Activity -
- - '); - - $stream = fopen('php://temp', 'w'); - $r = new OutputRules($stream, $this->html5->getOptions()); - $t = new Traverser($dom, $stream, $r, $this->html5->getOptions()); - - $list = $dom->getElementsByTagName('address'); - $r->element($list->item(0)); - $contents = stream_get_contents($stream, -1, 0); - - $this->assertMatchesRegularExpression('|
|', $contents); - $this->assertMatchesRegularExpression('|Dave Raggett,|', $contents); - $this->assertMatchesRegularExpression('|Arnaud Le Hors,|', $contents); - $this->assertMatchesRegularExpression('|contact persons for the W3C HTML Activity|', $contents); - $this->assertMatchesRegularExpression('|
|', $contents); - } - - /** - * Ensure direct DOM manipulation doesn't break TEXT_RAW elements (iframe, script, etc...). - */ - public function testHandlingInvalidRawContent() - { - $dom = $this->html5->loadHTML( - ' - - - - -'); - - $badNode = $dom->createElement('p', 'Bar'); - - // modify the content of the TEXT_RAW element: ')); - } - - public function testSvgAndMathElementsWithoutChildNodesAreHandledAsVoidTags() - { - $dom = $this->html5->loadHTML( - ' - - - - - -'); - - $contents = $this->html5->saveHTML($dom); - - self::assertMatchesRegularExpression('|^\h*$|m', $contents); - self::assertMatchesRegularExpression('|^\h*$|m', $contents); - } } From 4294b11dbc1b80f49399c69bf506e8120e158aa7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 18 Aug 2026 05:55:28 +0000 Subject: [PATCH 2/2] Address review feedback in shared serializer test refactor Co-authored-by: goetas <776743+goetas@users.noreply.github.com> --- .../Serializer/AbstractOutputRulesTest.php | 11 ++++++++--- test/HTML5/Serializer/OutputRulesNewDomTest.php | 17 ++++++++++------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/test/HTML5/Serializer/AbstractOutputRulesTest.php b/test/HTML5/Serializer/AbstractOutputRulesTest.php index c7f5c97..9f20f79 100644 --- a/test/HTML5/Serializer/AbstractOutputRulesTest.php +++ b/test/HTML5/Serializer/AbstractOutputRulesTest.php @@ -36,12 +36,17 @@ abstract protected function loadHTML($html); protected function createCommentNode($dom, $value) { - return new \DOMComment($value); + return $dom->createComment($value); } protected function createTextNode($dom, $value) { - return new \DOMText($value); + return $dom->createTextNode($value); + } + + protected function createElementWithText($dom, $name, $value) + { + return $dom->createElement($name, $value); } /** @@ -575,7 +580,7 @@ public function testHandlingInvalidRawContent() '); - $badNode = $dom->createElement('p', 'Bar'); + $badNode = $this->createElementWithText($dom, 'p', 'Bar'); // modify the content of the TEXT_RAW element: