diff --git a/src/Component.php b/src/Component.php index 0e727bb..03ff9eb 100644 --- a/src/Component.php +++ b/src/Component.php @@ -139,7 +139,31 @@ private function handleComponent( DOMElement $node, array $data ): bool { } $rendered = $this->app->renderComponentToDOM( $componentName, $componentData ); // TODO use adoptNode() instead of importNode() in PHP 8.3+ (see php-src commit ed6df1f0ad) - $node->replaceWith( $node->ownerDocument->importNode( $rendered, true ) ); + $importNode = $node->ownerDocument->importNode( $rendered, true ); + // TODO An issue in PHP 8.1.21's libxml integration causes a double-free if we replace a node + // directly with itself. T398821 + if ( $node != $importNode ) { + $node->replaceWith( $importNode ); + } else { + // TODO To work around the double-free, we detach all the children of the parent node and + // re-attach them in the correct sequence, replacing the target node with our newly-imported + // node. Once `mwcli` has moved off this outdated version of PHP (T388411) we should be able + // to remove this workaround. T398821 + $parent = $node->parentNode; + $children = []; + foreach ( iterator_to_array( $parent->childNodes ) as $child ) { + if ( $child !== $node ) { + $children[] = $child; + } else { + $children[] = $importNode; + } + $child->remove(); + } + + foreach ( $children as $child ) { + $parent->appendChild( $child ); + } + } return true; } diff --git a/tests/php/AppTest.php b/tests/php/AppTest.php index 1702a6c..1d3b1db 100644 --- a/tests/php/AppTest.php +++ b/tests/php/AppTest.php @@ -61,6 +61,16 @@ public function testNestedComponentObjectProp(): void { $this->assertSame( '

obj = { a: A, b: B }

', $result ); } + public function testComponentSubstitutionPreservesOrder(): void { + $app = new App( [] ); + $app->registerComponentTemplate( 'root', '

Following Text

' ); + $app->registerComponentTemplate( 'x-a', '

obj = { a: 1, b: 2 }

' ); + + $result = $app->renderComponent( 'root', [] ); + + $this->assertSame( '

obj = { a: 1, b: 2 }

Following Text

', $result ); + } + public function testComponentPropKebabCase(): void { $app = new App( [] ); $app->registerComponentTemplate(