diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php index fbd88417383..fdf61f7eceb 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php @@ -56,7 +56,6 @@ private function whenNodeAggregateWasMoved(NodeAggregateWasMoved $event): void ); $this->moveSubtreeTags( $event->contentStreamId, - $event->nodeAggregateId, $event->newParentNodeAggregateId, $succeedingSiblingForCoverage->dimensionSpacePoint ); diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php index 976526a10de..25887ab2575 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php @@ -134,81 +134,62 @@ private function whenSubtreeWasUntagged(SubtreeWasUntagged $event): void ]); } - private function moveSubtreeTags(ContentStreamId $contentStreamId, NodeAggregateId $nodeAggregateId, NodeAggregateId $newParentNodeAggregateId, DimensionSpacePoint $coveredDimensionSpacePoint): void - { - $nodeTags = $this->nodeTagsForNode($nodeAggregateId, $contentStreamId, $coveredDimensionSpacePoint); - $newParentSubtreeTags = $this->nodeTagsForNode($newParentNodeAggregateId, $contentStreamId, $coveredDimensionSpacePoint); - $newSubtreeTags = []; - foreach ($nodeTags->withoutInherited() as $tag) { - $newSubtreeTags[$tag->value] = true; - } - foreach ($newParentSubtreeTags as $tag) { - $newSubtreeTags[$tag->value] = null; - } - if ($newSubtreeTags === [] && $nodeTags->isEmpty()) { - return; - } + private function moveSubtreeTags( + ContentStreamId $contentStreamId, + NodeAggregateId $newParentNodeAggregateId, + DimensionSpacePoint $coveredDimensionSpacePoint + ): void { $this->getDatabaseConnection()->executeStatement(' - UPDATE ' . $this->getTableNamePrefix() . '_hierarchyrelation h - SET h.subtreetags = JSON_MERGE_PATCH(:newParentTags, JSON_MERGE_PATCH(\'{}\', h.subtreetags)) - WHERE h.childnodeanchor IN ( - WITH RECURSIVE cte (id) AS ( - SELECT ch.childnodeanchor - FROM ' . $this->getTableNamePrefix() . '_hierarchyrelation ch - INNER JOIN ' . $this->getTableNamePrefix() . '_node n ON n.relationanchorpoint = ch.parentnodeanchor + UPDATE ' . $this->getTableNamePrefix() . '_hierarchyrelation h, + ( + WITH RECURSIVE cte AS ( + SELECT + JSON_KEYS(th.subtreetags) subtreeTagsToInherit, th.childnodeanchor + FROM + ' . $this->getTableNamePrefix() . '_hierarchyrelation th + INNER JOIN ' . $this->getTableNamePrefix() . '_node tn ON tn.relationanchorpoint = th.childnodeanchor WHERE - n.nodeaggregateid = :nodeAggregateId - AND ch.contentstreamid = :contentStreamId - AND ch.dimensionspacepointhash = :dimensionSpacePointHash - UNION ALL + tn.nodeaggregateid = :newParentNodeAggregateId + AND th.contentstreamid = :contentStreamId + AND th.dimensionspacepointhash = :dimensionSpacePointHash + UNION SELECT - dh.childnodeanchor + JSON_MERGE_PRESERVE( + cte.subtreeTagsToInherit, + JSON_KEYS(JSON_MERGE_PATCH( + \'{}\', + dh.subtreetags + )) + ) subtreeTagsToInherit, + dh.childnodeanchor FROM cte - JOIN ' . $this->getTableNamePrefix() . '_hierarchyrelation dh ON dh.parentnodeanchor = cte.id + JOIN ' . $this->getTableNamePrefix() . '_hierarchyrelation dh + ON + dh.parentnodeanchor = cte.childnodeanchor + AND dh.contentstreamid = :contentStreamId + AND dh.dimensionspacepointhash = :dimensionSpacePointHash ) - SELECT id FROM cte + SELECT * FROM cte + ) AS r + SET h.subtreetags = ( + SELECT + JSON_MERGE_PATCH( + IFNULL(JSON_OBJECTAGG(htk.k, null), \'{}\'), + JSON_MERGE_PATCH(\'{}\', h.subtreetags) + ) + FROM + JSON_TABLE(r.subtreeTagsToInherit, \'$[*]\' COLUMNS (k VARCHAR(36) PATH \'$\')) htk ) - ', [ - 'contentStreamId' => $contentStreamId->value, - 'nodeAggregateId' => $nodeAggregateId->value, - 'dimensionSpacePointHash' => $coveredDimensionSpacePoint->hash, - 'newParentTags' => json_encode($newSubtreeTags, JSON_THROW_ON_ERROR | JSON_FORCE_OBJECT), - ]); - $this->getDatabaseConnection()->executeStatement(' - UPDATE ' . $this->getTableNamePrefix() . '_hierarchyrelation h - INNER JOIN ' . $this->getTableNamePrefix() . '_node n ON n.relationanchorpoint = h.childnodeanchor - SET h.subtreetags = :newParentTags WHERE - n.nodeaggregateid = :nodeAggregateId + h.childnodeanchor = r.childnodeanchor AND h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :dimensionSpacePointHash - ', [ - 'contentStreamId' => $contentStreamId->value, - 'nodeAggregateId' => $nodeAggregateId->value, - 'dimensionSpacePointHash' => $coveredDimensionSpacePoint->hash, - 'newParentTags' => json_encode($newSubtreeTags, JSON_THROW_ON_ERROR | JSON_FORCE_OBJECT), - ]); - } - - private function nodeTagsForNode(NodeAggregateId $nodeAggregateId, ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint): NodeTags - { - $subtreeTagsJson = $this->getDatabaseConnection()->fetchOne(' - SELECT h.subtreetags FROM ' . $this->getTableNamePrefix() . '_hierarchyrelation h - INNER JOIN ' . $this->getTableNamePrefix() . '_node n ON n.relationanchorpoint = h.childnodeanchor - WHERE - n.nodeaggregateid = :nodeAggregateId - AND h.contentstreamid = :contentStreamId - AND h.dimensionspacepointhash = :dimensionSpacePointHash ', [ - 'nodeAggregateId' => $nodeAggregateId->value, 'contentStreamId' => $contentStreamId->value, - 'dimensionSpacePointHash' => $dimensionSpacePoint->hash, + 'newParentNodeAggregateId' => $newParentNodeAggregateId->value, + 'dimensionSpacePointHash' => $coveredDimensionSpacePoint->hash, ]); - if (!is_string($subtreeTagsJson)) { - throw new \RuntimeException(sprintf('Failed to fetch SubtreeTags for node "%s" in content subgraph "%s@%s"', $nodeAggregateId->value, $dimensionSpacePoint->toJson(), $contentStreamId->value), 1698838865); - } - return NodeFactory::extractNodeTagsFromJson($subtreeTagsJson); } private function subtreeTagsForHierarchyRelation(ContentStreamId $contentStreamId, NodeRelationAnchorPoint $parentNodeAnchorPoint, DimensionSpacePoint $dimensionSpacePoint): NodeTags diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/05-MoveNodeAggregate_SubtreeTags.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/05-MoveNodeAggregate_SubtreeTags.feature index eb7d9ed22e4..ea431988e7d 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/05-MoveNodeAggregate_SubtreeTags.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/05-MoveNodeAggregate_SubtreeTags.feature @@ -458,7 +458,7 @@ Feature: Move a node aggregate into and out of a tagged parent Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" - And I expect this node to exactly inherit the tags "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} @@ -469,7 +469,7 @@ Feature: Move a node aggregate into and out of a tagged parent Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" - And I expect this node to exactly inherit the tags "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} @@ -539,7 +539,7 @@ Feature: Move a node aggregate into and out of a tagged parent Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" - And I expect this node to exactly inherit the tags "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} @@ -609,7 +609,7 @@ Feature: Move a node aggregate into and out of a tagged parent Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" - And I expect this node to exactly inherit the tags "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} @@ -679,7 +679,7 @@ Feature: Move a node aggregate into and out of a tagged parent Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" - And I expect this node to exactly inherit the tags "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} @@ -1020,7 +1020,7 @@ Feature: Move a node aggregate into and out of a tagged parent Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" - And I expect this node to exactly inherit the tags "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} @@ -1031,7 +1031,7 @@ Feature: Move a node aggregate into and out of a tagged parent Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" - And I expect this node to exactly inherit the tags "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} @@ -1101,7 +1101,7 @@ Feature: Move a node aggregate into and out of a tagged parent Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" - And I expect this node to exactly inherit the tags "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} @@ -1171,7 +1171,7 @@ Feature: Move a node aggregate into and out of a tagged parent Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" - And I expect this node to exactly inherit the tags "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} @@ -1241,7 +1241,7 @@ Feature: Move a node aggregate into and out of a tagged parent Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" - And I expect this node to exactly inherit the tags "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} @@ -1788,7 +1788,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to be exactly explicitly tagged "tag1" + And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} @@ -1811,7 +1811,7 @@ Feature: Move a node aggregate into and out of a tagged parent Given the command TagSubtree is executed with payload: | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | + | nodeAggregateId | "sir-david-nodenborough" | | coveredDimensionSpacePoint | {"example": "spec"} | | nodeVariantSelectionStrategy | "allSpecializations" | | tag | "tag1" | @@ -1881,7 +1881,7 @@ Feature: Move a node aggregate into and out of a tagged parent Given the command TagSubtree is executed with payload: | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | + | nodeAggregateId | "sir-david-nodenborough" | | coveredDimensionSpacePoint | {"example": "spec"} | | nodeVariantSelectionStrategy | "allSpecializations" | | tag | "tag1" | @@ -1951,7 +1951,7 @@ Feature: Move a node aggregate into and out of a tagged parent Given the command TagSubtree is executed with payload: | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | + | nodeAggregateId | "sir-david-nodenborough" | | coveredDimensionSpacePoint | {"example": "source"} | | nodeVariantSelectionStrategy | "allSpecializations" | | tag | "tag1" | @@ -2021,7 +2021,7 @@ Feature: Move a node aggregate into and out of a tagged parent Given the command TagSubtree is executed with payload: | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | + | nodeAggregateId | "sir-david-nodenborough" | | coveredDimensionSpacePoint | {"example": "source"} | | nodeVariantSelectionStrategy | "allSpecializations" | | tag | "tag1" | @@ -2091,7 +2091,7 @@ Feature: Move a node aggregate into and out of a tagged parent Given the command TagSubtree is executed with payload: | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | + | nodeAggregateId | "sir-david-nodenborough" | | coveredDimensionSpacePoint | {"example": "spec"} | | nodeVariantSelectionStrategy | "allSpecializations" | | tag | "tag1" | @@ -2161,7 +2161,7 @@ Feature: Move a node aggregate into and out of a tagged parent Given the command TagSubtree is executed with payload: | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | + | nodeAggregateId | "sir-david-nodenborough" | | coveredDimensionSpacePoint | {"example": "spec"} | | nodeVariantSelectionStrategy | "allSpecializations" | | tag | "tag1" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/SubtreeTagging/TagSubtree_WithoutDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/SubtreeTagging/TagSubtree_WithoutDimensions.feature index c88e62f53b3..6dc797cfa53 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/SubtreeTagging/TagSubtree_WithoutDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/SubtreeTagging/TagSubtree_WithoutDimensions.feature @@ -173,11 +173,11 @@ Feature: Tag subtree without dimensions """ b (tag2*) b1 (tag3*,tag2) - a1a (tag4*,tag3,tag2) - a1a1 (tag4*,tag1*,tag3,tag2) - a1a1a (tag4*,tag3,tag2) - a1a1b (tag4*,tag3,tag2) - a1a2 (tag4*,tag3,tag2) + a1a (tag4*,tag2,tag3) + a1a1 (tag1*,tag2,tag3,tag4) + a1a1a (tag1,tag2,tag3,tag4) + a1a1b (tag1,tag2,tag3,tag4) + a1a2 (tag2,tag3,tag4) """ When the command CreateNodeAggregateWithNode is executed with payload: @@ -189,12 +189,12 @@ Feature: Tag subtree without dimensions """ b (tag2*) b1 (tag3*,tag2) - a1a (tag4*,tag3,tag2) - a1a1 (tag4*,tag1*,tag3,tag2) - a1a1a (tag4*,tag3,tag2) - a1a1b (tag4*,tag3,tag2) - a1a2 (tag4*,tag3,tag2) - a1a3 (tag4,tag3,tag2) + a1a (tag4*,tag2,tag3) + a1a1 (tag1*,tag2,tag3,tag4) + a1a1a (tag1,tag2,tag3,tag4) + a1a1b (tag1,tag2,tag3,tag4) + a1a2 (tag2,tag3,tag4) + a1a3 (tag2,tag3,tag4) """ When the command UntagSubtree is executed with payload: @@ -206,10 +206,10 @@ Feature: Tag subtree without dimensions """ b (tag2*) b1 (tag3*,tag2) - a1a (tag3,tag2) - a1a1 (tag4*,tag1*,tag3,tag2) - a1a1a (tag4*,tag3,tag2) - a1a1b (tag4*,tag3,tag2) - a1a2 (tag4*,tag3,tag2) - a1a3 (tag3,tag2) + a1a (tag2,tag3) + a1a1 (tag1*,tag2,tag3) + a1a1a (tag1,tag2,tag3) + a1a1b (tag1,tag2,tag3) + a1a2 (tag2,tag3) + a1a3 (tag2,tag3) """ diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/NodeTags.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/NodeTags.php index 8317643ab22..06794251a50 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/NodeTags.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/NodeTags.php @@ -23,7 +23,7 @@ * * Internally, this consists of two collection: * - One for tags that are _explicitly_ set on the respective node. - * - And one that contains tags that are _inherited_ by one of the ancestor nodes + * - And one that contains tags that are only _inherited_ by one of the ancestor nodes and _not_ explicitly set * * In most cases, it shouldn't matter whether a tag is inherited or set explicitly. But sometimes the behavior is slightly different (e.g. the "disabled" checkbox in the Neos UI inspector is only checked if the node has the `disabled` tag set explicitly) * diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/NodeTraversalTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/NodeTraversalTrait.php index 119ed394de0..098598b9c2a 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/NodeTraversalTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/NodeTraversalTrait.php @@ -248,7 +248,11 @@ public function iExecuteTheFindSubtreeQueryIExpectTheFollowingTrees(string $entr $subtree = array_shift($subtreeStack); $tags = []; if ($withTags !== null) { - $tags = [...array_map(static fn(string $tag) => $tag . '*', $subtree->node->tags->withoutInherited()->toStringArray()), ...$subtree->node->tags->onlyInherited()->toStringArray()]; + $explicitTags = $subtree->node->tags->withoutInherited()->toStringArray(); + sort($explicitTags); + $inheritedTags = $subtree->node->tags->onlyInherited()->toStringArray(); + sort($inheritedTags); + $tags = [...array_map(static fn(string $tag) => $tag . '*', $explicitTags), ...$inheritedTags]; } $result[] = str_repeat(' ', $subtree->level) . $subtree->node->nodeAggregateId->value . ($tags !== [] ? ' (' . implode(',', $tags) . ')' : ''); $subtreeStack = [...$subtree->children, ...$subtreeStack]; diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php index bee6d1ffec9..5b6d14e9c82 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php @@ -243,23 +243,33 @@ public function iExpectTheNodeWithAggregateIdentifierToNotContainTheTag(string $ /** * @Then /^I expect this node to be exactly explicitly tagged "(.*)"$/ - * @param string $tagList the comma-separated list of tag names + * @param string $expectedTagList the comma-separated list of tag names */ - public function iExpectThisNodeToBeExactlyExplicitlyTagged(string $tagList): void + public function iExpectThisNodeToBeExactlyExplicitlyTagged(string $expectedTagList): void { - $this->assertOnCurrentNode(function (Node $currentNode) use ($tagList) { - $currentNode->tags->withoutInherited()->toStringArray() === explode(',', $tagList); + $this->assertOnCurrentNode(function (Node $currentNode) use ($expectedTagList) { + $actualTags = $currentNode->tags->withoutInherited()->toStringArray(); + sort($actualTags); + Assert::assertSame( + ($expectedTagList === '') ? [] : explode(',', $expectedTagList), + $actualTags + ); }); } /** * @Then /^I expect this node to exactly inherit the tags "(.*)"$/ - * @param string $tagList the comma-separated list of tag names + * @param string $expectedTagList the comma-separated list of tag names */ - public function iExpectThisNodeToExactlyInheritTheTags(string $tagList): void + public function iExpectThisNodeToExactlyInheritTheTags(string $expectedTagList): void { - $this->assertOnCurrentNode(function (Node $currentNode) use ($tagList) { - $currentNode->tags->onlyInherited()->toStringArray() === explode(',', $tagList); + $this->assertOnCurrentNode(function (Node $currentNode) use ($expectedTagList) { + $actualTags = $currentNode->tags->onlyInherited()->toStringArray(); + sort($actualTags); + Assert::assertSame( + ($expectedTagList === '') ? [] : explode(',', $expectedTagList), + $actualTags, + ); }); }