Skip to content

Commit

Permalink
Merge pull request #3822 from neos/bugfix/node-tree-presets-inconsist…
Browse files Browse the repository at this point in the history
…encies

BUGFIX: Fix inconsistencies in document tree with node tree presets
  • Loading branch information
Sebobo authored Jul 23, 2024
2 parents fda27d7 + 9e83031 commit bc789ca
Show file tree
Hide file tree
Showing 35 changed files with 1,384 additions and 102 deletions.
4 changes: 3 additions & 1 deletion Classes/Controller/BackendServiceController.php
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,7 @@ function ($envelope) {

$nodeInfoHelper = new NodeInfoHelper();
$result = [];

switch ($finisher['type']) {
case 'get':
$result = $nodeInfoHelper->renderNodes(array_filter($flowQuery->get()), $this->getControllerContext());
Expand All @@ -553,7 +554,8 @@ function ($envelope) {
$result = $nodeInfoHelper->renderNodes(array_filter($flowQuery->get()), $this->getControllerContext(), true);
break;
case 'getForTreeWithParents':
$result = $nodeInfoHelper->renderNodesWithParents(array_filter($flowQuery->get()), $this->getControllerContext());
$nodeTypeFilter = $finisher['payload']['nodeTypeFilter'] ?? null;
$result = $nodeInfoHelper->renderNodesWithParents(array_filter($flowQuery->get()), $this->getControllerContext(), $nodeTypeFilter);
break;
}

Expand Down
5 changes: 3 additions & 2 deletions Classes/Fusion/Helper/NodeInfoHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -275,9 +275,10 @@ public function renderNodes(array $nodes, ControllerContext $controllerContext,
/**
* @param array $nodes
* @param ControllerContext $controllerContext
* @param null|string $nodeTypeFilter
* @return array
*/
public function renderNodesWithParents(array $nodes, ControllerContext $controllerContext): array
public function renderNodesWithParents(array $nodes, ControllerContext $controllerContext, ?string $nodeTypeFilter = null): array
{
// For search operation we want to include all nodes, not respecting the "baseNodeType" setting
$baseNodeTypeOverride = $this->documentNodeTypeRole;
Expand All @@ -287,7 +288,7 @@ public function renderNodesWithParents(array $nodes, ControllerContext $controll
foreach ($nodes as $node) {
if (array_key_exists($node->getPath(), $renderedNodes)) {
$renderedNodes[$node->getPath()]['matched'] = true;
} elseif ($renderedNode = $this->renderNodeWithMinimalPropertiesAndChildrenInformation($node, $controllerContext, $baseNodeTypeOverride)) {
} elseif ($renderedNode = $this->renderNodeWithMinimalPropertiesAndChildrenInformation($node, $controllerContext, $nodeTypeFilter ?? $baseNodeTypeOverride)) {
$renderedNode['matched'] = true;
$renderedNodes[$node->getPath()] = $renderedNode;
} else {
Expand Down

Large diffs are not rendered by default.

257 changes: 257 additions & 0 deletions Tests/IntegrationTests/Fixtures/1Dimension/nodeTreePresets.e2e.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
import {beforeEach, checkPropTypes} from './../../utils.js';
import {Page} from './../../pageModel';
import {Selector} from 'testcafe';

/* global fixture:true */

fixture`Node Tree Presets`
.beforeEach(beforeEach)
.afterEach(() => checkPropTypes())
.before(async () => {
const response = await fetch('http://127.0.0.1:8081/test/write-additional-settings', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({settings: SETTINGS_WITH_NODE_TREE_PRESETS})
});
const json = await response.json();
if (!('success' in json) || !json.success) {
throw new Error('Additional settings could not be written.');
}
})
.after(async () => {
const response = await fetch('http://127.0.0.1:8081/test/remove-additional-settings', {
method: 'POST'
});
const json = await response.json();
if (!('success' in json) || !json.success) {
throw new Error('Additional settings could not be removed.');
}
});

const SETTINGS_WITH_NODE_TREE_PRESETS = {
Neos: {
Neos: {
userInterface: {
navigateComponent: {
nodeTree: {
loadingDepth: 2,
presets: {
'default': {
baseNodeType: 'Neos.Neos:Document,!Neos.TestNodeTypes:Document.Blog,!Neos.TestNodeTypes:Document.BlogArticle'
},
'blog': {
ui: {
icon: 'newspaper-o',
label: 'Show Blog only'
},
baseNodeType: 'Neos.TestNodeTypes:Document.Blog'
},
'blog-articles': {
ui: {
icon: 'file-text-o',
label: 'Show Blog Articles only'
},
baseNodeType: 'Neos.TestNodeTypes:Document.BlogArticle'
}
}
}
}
}
}
}
};

test('Node tree preset "default" removes all blog related nodes and only loads nodes with depth <= 2', async (t) => {
//
// Assert that all documents with a depth > 2 are no longer visible in
// "default" preset
//
await t.expect(Page.treeNode.withExactText('Nested Page #2').exists)
.notOk('[🗋 Nested Page #2] can still be found in the document tree with preset "default".');

//
// Assert that all the blog-related documents are not visible in "default"
// preset
//
await t.expect(Page.treeNode.withExactText('Blog').exists)
.notOk('[🗋 Blog] can still be found in the document tree with preset "default".');
await t.expect(Page.treeNode.withExactText('Hello World!').exists)
.notOk('[🗋 Hello World!] can still be found in the document tree with preset "default".');
await t.expect(Page.treeNode.withExactText('Fix all the bugs with this weird little trick!').exists)
.notOk('[🗋 Fix all the bugs with this weird little trick!] can still be found in the document tree with preset "default".');
await t.expect(Page.treeNode.withExactText('Writing Blog Articles considered harmful').exists)
.notOk('[🗋 Writing Blog Articles considered harmful] can still be found in the document tree with preset "default".');
});

test('Node tree preset "blog" shows nothing but page [🗋 Blog]', async (t) => {
await t.click('#btn-ToggleDocumentTreeFilter');
await t.click('#neos-NodeTreeFilter');
await t.click(Selector('[role="button"]').withText('Show Blog only'));

await t.expect(Page.treeNode.withExactText('Blog').exists)
.ok('[🗋 Blog] did not show up after switching to node tree preset "blog".');
});

test('In node tree preset "blog", page [🗋 Blog] has no toggle handle', async (t) => {
await t.click('#btn-ToggleDocumentTreeFilter');
await t.click('#neos-NodeTreeFilter');
await t.click(Selector('[role="button"]').withText('Show Blog only'));

await t.expect(Page.getToggleChildrenButtonOf('Blog').exists)
.notOk('[🗋 Blog] has a toggle handle, even though its children do not match the currently set filter in node tree preset "blog".');
});

test('Reloading the node tree while in preset "blog" results in nothing but page [🗋 Blog]', async (t) => {
await t.click('#btn-ToggleDocumentTreeFilter');
await t.click('#neos-NodeTreeFilter');
await t.click(Selector('[role="button"]').withText('Show Blog only'));
await t.click('#neos-PageTree-RefreshPageTree');

await t.expect(Page.treeNode.withExactText('Blog').exists)
.ok('[🗋 Blog] did not show up after switching to node tree preset "blog".');
});

test('Node tree preset "blog-articles" shows page [🗋 Blog] and all articles beneath it', async (t) => {
await t.click('#btn-ToggleDocumentTreeFilter');
await t.click('#neos-NodeTreeFilter');
await t.click(Selector('[role="button"]').withText('Show Blog Articles only'));

await t.expect(Page.treeNode.withExactText('Blog').exists)
.ok('[🗋 Blog] did not show up after switching to node tree preset "blog-articles".');
await t.expect(Page.treeNode.withExactText('Hello World!').exists)
.ok('[🗋 Hello World!] did not show up after switching to node tree preset "blog-articles".');
await t.expect(Page.treeNode.withExactText('Fix all the bugs with this weird little trick!').exists)
.ok('[🗋 Fix all the bugs with this weird little trick!] did not show up after switching to node tree preset "blog-articles".');
await t.expect(Page.treeNode.withExactText('Writing Blog Articles considered harmful').exists)
.ok('[🗋 Writing Blog Articles considered harmful] did not show up after switching to node tree preset "blog-articles".');
});

//
// Original issue: https://github.com/neos/neos-ui/issues/3816
//
test('BUG #3816: Switching back from node tree preset "blog" does not affect loading children in node tree preset "default"', async (t) => {
await t.click(Page.getToggleChildrenButtonOf('Nested Page #1'));
await t.expect(Page.treeNode.withExactText('Nested Page #2').exists)
.ok('[🗋 Nested Page #2] did not show up after toggling children of [🗋 Nested Page #1] the first time.');
await t.click(Page.getToggleChildrenButtonOf('Nested Page #1'));
await t.expect(Page.treeNode.withExactText('Nested Page #2').exists)
.notOk('[🗋 Nested Page #2] did not disappear after toggling children of [🗋 Nested Page #1] the second time.');

await t.click('#btn-ToggleDocumentTreeFilter');
await t.click('#neos-NodeTreeFilter');
await t.click(Selector('[role="button"]').withText('Show Blog only'));
await t.expect(Page.treeNode.withExactText('Blog').exists)
.ok('[🗋 Blog] did not show up after switching to node tree preset "blog".');

await t.click('#neos-NodeTreeFilter-SelectBox-btn-reset');
await t.expect(Page.treeNode.withExactText('Blog').exists)
.notOk('[🗋 Blog] did not disappear after switching back to node tree preset "default".');

await t.click(Page.getToggleChildrenButtonOf('Nested Page #1'));
await t.expect(Page.treeNode.withExactText('Nested Page #2').exists)
.ok('[🗋 Nested Page #2] did not show up after toggling children of [🗋 Nested Page #1] the third time.');
await t.click(Page.getToggleChildrenButtonOf('Nested Page #1'));
await t.expect(Page.treeNode.withExactText('Nested Page #2').exists)
.notOk('[🗋 Nested Page #2] did not disappear after toggling children of [🗋 Nested Page #1] the fourth time.');
});

//
// Original issue: https://github.com/neos/neos-ui/issues/2583
//
test('BUG #2583: Searching the document tree does not break expansion in node tree preset "default"', async (t) => {
await t.click(Page.getToggleChildrenButtonOf('Nested Page #1'));
await t.expect(Page.treeNode.withExactText('Nested Page #2').exists)
.ok('[🗋 Nested Page #2] did not show up after toggling children of [🗋 Nested Page #1] the first time.');
await t.click(Page.getToggleChildrenButtonOf('Nested Page #1'));
await t.expect(Page.treeNode.withExactText('Nested Page #2').exists)
.notOk('[🗋 Nested Page #2] did not disappear after toggling children of [🗋 Nested Page #1] the second time.');

await t.click('#btn-ToggleDocumentTreeFilter');

await t.typeText(Selector('#neos-NodeTreeSearchInput input[type="search"]'), 'Nested Page #10');
await t.expect(Page.treeNode.withExactText('Nested Page #10').exists)
.ok('[🗋 Nested Page #10] did not show up after searching for "Nested Page #10".');

await t.click('#neos-NodeTreeSearchInput-btn-reset');
await t.expect(Page.treeNode.withExactText('Nested Page #10').exists)
.notOk('[🗋 Nested Page #10] did not disappear after clearing search.');

await t.click(Page.getToggleChildrenButtonOf('Nested Page #1'));
await t.expect(Page.treeNode.withExactText('Nested Page #2').exists)
.ok('[🗋 Nested Page #2] did not show up after toggling children of [🗋 Nested Page #1] the third time.');
await t.click(Page.getToggleChildrenButtonOf('Nested Page #1'));
await t.expect(Page.treeNode.withExactText('Nested Page #2').exists)
.notOk('[🗋 Nested Page #2] did not disappear after toggling children of [🗋 Nested Page #1] the fourth time.');
});

//
// Original issue: https://github.com/neos/neos-ui/issues/2800
//
test('BUG #2800 1/2: Moving pages before/after in a filtered view does not lead to the disappearance of nodes', async (t) => {
await t.click('#btn-ToggleDocumentTreeFilter');
await t.click('#neos-NodeTreeFilter');
await t.click(Selector('[role="button"]').withText('Show Blog Articles only'));

//
// Move Blog Article [🗋 Hello World!] before [🗋 Writing Blog Articles considered harmful]
//
await t.dragToElement(
Page.getTreeNodeButton('Hello World!'),
Page.getTreeNodeButton('Writing Blog Articles considered harmful').prevSibling()
);

await t.expect(Page.treeNode.withExactText('Blog').exists)
.ok('[🗋 Blog] disappeared after moving nodes in node tree preset "blog-articles".');
await t.expect(Page.treeNode.withExactText('Hello World!').exists)
.ok('[🗋 Hello World!] disappeared after moving nodes in node tree preset "blog-articles".');
await t.expect(Page.treeNode.withExactText('Fix all the bugs with this weird little trick!').exists)
.ok('[🗋 Fix all the bugs with this weird little trick!] disappeared after moving nodes in node tree preset "blog-articles".');
await t.expect(Page.treeNode.withExactText('Writing Blog Articles considered harmful').exists)
.ok('[🗋 Writing Blog Articles considered harmful] disappeared after moving nodes in node tree preset "blog-articles".');

//
// Move Blog Article [🗋 Hello World!] after [🗋 Writing Blog Articles considered harmful]
//
await t.dragToElement(
Page.getTreeNodeButton('Hello World!'),
Page.getTreeNodeButton('Writing Blog Articles considered harmful').nextSibling()
);

await t.expect(Page.treeNode.withExactText('Blog').exists)
.ok('[🗋 Blog] disappeared after moving nodes in node tree preset "blog-articles".');
await t.expect(Page.treeNode.withExactText('Hello World!').exists)
.ok('[🗋 Hello World!] disappeared after moving nodes in node tree preset "blog-articles".');
await t.expect(Page.treeNode.withExactText('Fix all the bugs with this weird little trick!').exists)
.ok('[🗋 Fix all the bugs with this weird little trick!] disappeared after moving nodes in node tree preset "blog-articles".');
await t.expect(Page.treeNode.withExactText('Writing Blog Articles considered harmful').exists)
.ok('[🗋 Writing Blog Articles considered harmful] disappeared after moving nodes in node tree preset "blog-articles".');
});

test('BUG #2800 2/2: Moving pages into each other in a filtered view does not break expansion', async (t) => {
await t.click('#btn-ToggleDocumentTreeFilter');
await t.click('#neos-NodeTreeFilter');
await t.click(Selector('[role="button"]').withText('Show Blog Articles only'));

//
// Move Blog Article [🗋 Hello World!] into [🗋 Writing Blog Articles considered harmful]
//
await t.dragToElement(
Page.getTreeNodeButton('Hello World!'),
Page.getTreeNodeButton('Writing Blog Articles considered harmful')
);

await t.expect(Page.treeNode.withExactText('Blog').exists)
.ok('[🗋 Blog] disappeared after moving nodes in node tree preset "blog-articles".');
await t.expect(Page.treeNode.withExactText('Fix all the bugs with this weird little trick!').exists)
.ok('[🗋 Fix all the bugs with this weird little trick!] disappeared after moving nodes in node tree preset "blog-articles".');
await t.expect(Page.treeNode.withExactText('Writing Blog Articles considered harmful').exists)
.ok('[🗋 Writing Blog Articles considered harmful] disappeared after moving nodes in node tree preset "blog-articles".');

await t.expect(Page.treeNode.withExactText('Hello World!').exists)
.notOk('[🗋 Hello World!] unexpectedly appeared before uncollapsing [🗋 Writing Blog Articles considered harmful].');
await t.click(Page.getToggleChildrenButtonOf('Writing Blog Articles considered harmful'));
await t.expect(Page.treeNode.withExactText('Hello World!').exists)
.ok('[🗋 Writing Blog Articles considered harmful] cannot be uncollapsed after [🗋 Hello World!] was moved into it in node tree preset "blog-articles".');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

/*
* This file is part of the Neos.Neos.Ui package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

declare(strict_types=1);

namespace Neos\TestNodeTypes\Application\RemoveAdditionalSettings\Controller;

use Neos\Flow\Annotations as Flow;
use Neos\Flow\Mvc\ActionRequest;
use Neos\Flow\Mvc\ActionResponse;
use Neos\Flow\Mvc\Controller\ControllerInterface;
use Neos\TestNodeTypes\Application\RemoveAdditionalSettings\RemoveAdditionalSettingsCommand;
use Neos\TestNodeTypes\Application\RemoveAdditionalSettings\RemoveAdditionalSettingsCommandHandler;

#[Flow\Scope("singleton")]
final class RemoveAdditionalSettingsController implements ControllerInterface
{
#[Flow\Inject]
protected RemoveAdditionalSettingsCommandHandler $commandHandler;

public function processRequest(ActionRequest $request, ActionResponse $response)
{
$request->setDispatched(true);
$response->setContentType('application/json');

try {
$command = RemoveAdditionalSettingsCommand::fromArray($request->getArguments());
$this->commandHandler->handle($command);

$response->setStatusCode(200);
$response->setContent(
json_encode(
['success' => true],
JSON_THROW_ON_ERROR
)
);
} catch (\InvalidArgumentException $e) {
$response->setStatusCode(400);
$response->setContent(
json_encode(
['error' => [
'type' => $e::class,
'code' => $e->getCode(),
'message' => $e->getMessage(),
]],
JSON_THROW_ON_ERROR
)
);
} catch (\Exception $e) {
$response->setStatusCode(500);
$response->setContent(
json_encode(
['error' => [
'type' => $e::class,
'code' => $e->getCode(),
'message' => $e->getMessage(),
]],
JSON_THROW_ON_ERROR
)
);
}
}
}
Loading

0 comments on commit bc789ca

Please sign in to comment.