Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/8.3' into 9.0
Browse files Browse the repository at this point in the history
  • Loading branch information
mhsdesign committed Oct 31, 2023
2 parents 724f8a3 + 0a40e86 commit 821322b
Show file tree
Hide file tree
Showing 5 changed files with 974 additions and 0 deletions.
184 changes: 184 additions & 0 deletions Tests/Behavior/Features/Bootstrap/FusionTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
<?php
declare(strict_types=1);

/*
* This file is part of the Neos.ContentRepository 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.
*/

namespace Neos\Neos\Tests\Behavior\Features\Bootstrap;

use Behat\Gherkin\Node\PyStringNode;
use Neos\ContentRepository\Tests\Behavior\Features\Bootstrap\NodeOperationsTrait;
use Neos\Eel\FlowQuery\FlowQuery;
use Neos\Flow\Mvc\ActionRequest;
use Neos\Flow\Mvc\ActionResponse;
use Neos\Flow\Mvc\Controller\Arguments;
use Neos\Flow\Mvc\Controller\ControllerContext;
use Neos\Flow\Mvc\Routing\UriBuilder;
use Neos\Flow\Tests\FunctionalTestRequestHandler;
use Neos\Flow\Tests\Unit\Http\Fixtures\SpyRequestHandler;
use Neos\Fusion\Core\FusionSourceCodeCollection;
use Neos\Fusion\Core\Parser;
use Neos\Fusion\Core\RuntimeFactory;
use Neos\Neos\Domain\Service\ContentContext;
use Neos\Neos\Routing\RequestUriHostMiddleware;
use PHPUnit\Framework\Assert;
use Psr\Http\Message\ServerRequestFactoryInterface;
use Psr\Http\Message\ServerRequestInterface;

/**
* @internal only for behat tests within the Neos.Neos package
*/
trait FusionTrait
{
use NodeOperationsTrait;

private ?ActionRequest $fusionRequest = null;
private array $fusionContext = [];

private ?string $renderingResult = null;

private ?string $fusionCode = null;

private ?\Throwable $lastRenderingException = null;

/**
* @BeforeScenario
*/
public function setupFusionContext(): void
{
$this->fusionRequest = null;
$this->fusionContext = [];
$this->fusionCode = null;
$this->renderingResult = null;
}

/**
* @When the Fusion context node is :nodeIdentifier
*/
public function theFusionContextNodeIs(string $nodeIdentifier): void
{
/** @var ContentContext $context */
$context = $this->getContextForProperties([]);
$this->fusionContext['node'] = $context->getNodeByIdentifier($nodeIdentifier);
if ($this->fusionContext['node'] === null) {
throw new \InvalidArgumentException(sprintf('Node with identifier "%s" could not be found in the "%s" workspace', $nodeIdentifier, $context->getWorkspaceName()), 1696700222);
}
$flowQuery = new FlowQuery([$this->fusionContext['node']]);
$this->fusionContext['documentNode'] = $flowQuery->closest('[instanceof Neos.Neos:Document]')->get(0);
if ($this->fusionContext['documentNode'] === null) {
throw new \RuntimeException(sprintf('Failed to find closest document node for node with identifier "%s"', $nodeIdentifier), 1697790940);
}
$this->fusionContext['site'] = $context->getCurrentSiteNode();
if ($this->fusionContext['site'] === null) {
throw new \RuntimeException(sprintf('Failed to resolve site node for node with identifier "%s"', $nodeIdentifier), 1697790963);
}
}

/**
* @When the Fusion context request URI is :requestUri
*/
public function theFusionContextRequestIs(string $requestUri = null): void
{
$httpRequest = $this->objectManager->get(ServerRequestFactoryInterface::class)->createServerRequest('GET', $requestUri);
$httpRequest = $this->addRoutingParameters($httpRequest);

$this->fusionRequest = ActionRequest::fromHttpRequest($httpRequest);
}

private function addRoutingParameters(ServerRequestInterface $httpRequest): ServerRequestInterface
{
$spyMiddleware = new SpyRequestHandler();
(new RequestUriHostMiddleware())->process($httpRequest, $spyMiddleware);
return $spyMiddleware->getHandledRequest();
}

/**
* @When I have the following Fusion setup:
*/
public function iHaveTheFollowingFusionSetup(PyStringNode $fusionCode): void
{
$this->fusionCode = $fusionCode->getRaw();
}

/**
* @When I execute the following Fusion code:
* @When I execute the following Fusion code on path :path:
*/
public function iExecuteTheFollowingFusionCode(PyStringNode $fusionCode, string $path = 'test'): void
{
if ($this->fusionRequest === null) {
$this->theFusionContextRequestIs('http://localhost');
}
$requestHandler = new FunctionalTestRequestHandler(self::$bootstrap);
$requestHandler->setHttpRequest($this->fusionRequest->getHttpRequest());
self::$bootstrap->setActiveRequestHandler($requestHandler);
$this->throwExceptionIfLastRenderingLedToAnError();
$this->renderingResult = null;
$fusionAst = (new Parser())->parseFromSource(FusionSourceCodeCollection::fromString($this->fusionCode . chr(10) . $fusionCode->getRaw()));
$uriBuilder = new UriBuilder();
$uriBuilder->setRequest($this->fusionRequest);
$controllerContext = new ControllerContext($this->fusionRequest, new ActionResponse(), new Arguments(), $uriBuilder);

$fusionRuntime = (new RuntimeFactory())->createFromConfiguration($fusionAst, $controllerContext);
$fusionRuntime->pushContextArray($this->fusionContext);
try {
$this->renderingResult = $fusionRuntime->render($path);
} catch (\Throwable $exception) {
$this->lastRenderingException = $exception;
}
$fusionRuntime->popContext();
}

/**
* @Then I expect the following Fusion rendering result:
*/
public function iExpectTheFollowingFusionRenderingResult(PyStringNode $expectedResult): void
{
Assert::assertSame($expectedResult->getRaw(), $this->renderingResult);
}

/**
* @Then I expect the following Fusion rendering result as HTML:
*/
public function iExpectTheFollowingFusionRenderingResultAsHtml(PyStringNode $expectedResult): void
{
Assert::assertIsString($this->renderingResult, 'Previous Fusion rendering did not produce a string');
$stripWhitespace = static fn (string $input): string => preg_replace(['/>[^\S ]+/s', '/[^\S ]+</s', '/(\s)+/s', '/> </s'], ['>', '<', '\\1', '><'], $input);

$expectedDom = new \DomDocument();
$expectedDom->preserveWhiteSpace = false;
$expectedDom->loadHTML($stripWhitespace($expectedResult->getRaw()));

$actualDom = new \DomDocument();
$actualDom->preserveWhiteSpace = false;
$actualDom->loadHTML($stripWhitespace($this->renderingResult));

Assert::assertSame($expectedDom->saveHTML(), $actualDom->saveHTML());
}
/**
* @Then I expect the following Fusion rendering error:
*/
public function iExpectTheFollowingFusionRenderingError(PyStringNode $expectedError): void
{
Assert::assertNotNull($this->lastRenderingException, 'The previous rendering did not lead to an error');
Assert::assertSame($expectedError->getRaw(), $this->lastRenderingException->getMessage());
$this->lastRenderingException = null;
}

/**
* @AfterScenario
*/
public function throwExceptionIfLastRenderingLedToAnError(): void
{
if ($this->lastRenderingException !== null) {
throw new \RuntimeException(sprintf('The last rendering led to an error: %s', $this->lastRenderingException->getMessage()), 1698319254, $this->lastRenderingException);
}
}
}
60 changes: 60 additions & 0 deletions Tests/Behavior/Features/Fusion/ContentCase.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
@fixtures
Feature: Tests for the "Neos.Neos:ContentCase" Fusion prototype

Background:
Given I have the site "a"
And I have the following NodeTypes configuration:
"""yaml
'unstructured': {}
'Neos.Neos:FallbackNode': {}
'Neos.Neos:Document': {}
'Neos.Neos:Test.DocumentType1':
superTypes:
'Neos.Neos:Document': true
'Neos.Neos:Test.DocumentType2':
superTypes:
'Neos.Neos:Document': true
"""
And I have the following nodes:
| Identifier | Path | Node Type |
| root | /sites | unstructured |
| a | /sites/a | Neos.Neos:Test.DocumentType1 |
| a1 | /sites/a/a1 | Neos.Neos:Test.DocumentType2 |
And the Fusion context node is "a1"
And the Fusion context request URI is "http://localhost"

Scenario: ContentCase without corresponding implementation
When I execute the following Fusion code:
"""fusion
include: resource://Neos.Fusion/Private/Fusion/Root.fusion
include: resource://Neos.Neos/Private/Fusion/Root.fusion
test = Neos.Neos:ContentCase
"""
Then I expect the following Fusion rendering error:
"""
The Fusion object "Neos.Neos:Test.DocumentType2" cannot be rendered:
Most likely you mistyped the prototype name or did not define
the Fusion prototype with "prototype(Neos.Neos:Test.DocumentType2) < prototype(...)".
Other possible reasons are a missing parent-prototype or
a missing "@class" annotation for prototypes without parent.
It is also possible your Fusion file is not read because
of a missing "include:" statement.
"""

Scenario: ContentCase with corresponding implementation
When I execute the following Fusion code:
"""fusion
include: resource://Neos.Fusion/Private/Fusion/Root.fusion
include: resource://Neos.Neos/Private/Fusion/Root.fusion
prototype(Neos.Neos:Test.DocumentType2) < prototype(Neos.Fusion:Value) {
value = 'implementation for DocumentType2'
}
test = Neos.Neos:ContentCase
"""
Then I expect the following Fusion rendering result:
"""
implementation for DocumentType2
"""
94 changes: 94 additions & 0 deletions Tests/Behavior/Features/Fusion/ContentCollection.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
@fixtures
Feature: Tests for the "Neos.Neos:ContentCollection" Fusion prototype

Background:
Given I have the site "a"
And I have the following NodeTypes configuration:
"""yaml
'unstructured': {}
'Neos.Neos:FallbackNode': {}
'Neos.Neos:Document': {}
'Neos.Neos:ContentCollection': {}
'Neos.Neos:Content': {}
'Neos.Neos:Test.DocumentType':
superTypes:
'Neos.Neos:Document': true
childNodes:
main:
type: 'Neos.Neos:ContentCollection'
'Neos.Neos:Test.ContentType':
superTypes:
'Neos.Neos:Content': true
"""
And I have the following nodes:
| Identifier | Path | Node Type |
| root | /sites | unstructured |
| a | /sites/a | Neos.Neos:Test.DocumentType |
And the Fusion context node is "a"
And the Fusion context request URI is "http://localhost"

# Scenario: missing Neos.Neos.ContentCollection node
# When I execute the following Fusion code:
# """fusion
# include: resource://Neos.Fusion/Private/Fusion/Root.fusion
# include: resource://Neos.Neos/Private/Fusion/Root.fusion
#
# test = Neos.Neos:ContentCollection
# """
# Then I expect the following Fusion rendering error:
# """
# No content collection of type Neos.Neos:ContentCollection could be found in the current node (/sites/a) or at the path "to-be-set-by-user". You might want to adjust your node type configuration and create the missing child node through the "./flow node:repair --node-type Neos.Neos:Test.DocumentType" command.
# """
#
# Scenario: invalid nodePath
# When I execute the following Fusion code:
# """fusion
# include: resource://Neos.Fusion/Private/Fusion/Root.fusion
# include: resource://Neos.Neos/Private/Fusion/Root.fusion
#
# test = Neos.Neos:ContentCollection {
# nodePath = 'invalid'
# }
# """
# Then I expect the following Fusion rendering error:
# """
# No content collection of type Neos.Neos:ContentCollection could be found in the current node (/sites/a) or at the path "invalid". You might want to adjust your node type configuration and create the missing child node through the "./flow node:repair --node-type Neos.Neos:Test.DocumentType" command.
# """

Scenario: empty ContentCollection
When I execute the following Fusion code:
"""fusion
include: resource://Neos.Fusion/Private/Fusion/Root.fusion
include: resource://Neos.Neos/Private/Fusion/Root.fusion
test = Neos.Neos:ContentCollection {
nodePath = 'main'
}
"""
Then I expect the following Fusion rendering result as HTML:
"""
<div class="neos-contentcollection"></div>
"""

Scenario:
When I have the following nodes:
| Identifier | Path | Node Type |
| content1 | /sites/a/main/content1 | Neos.Neos:Test.ContentType |
| content2 | /sites/a/main/content2 | Neos.Neos:Test.ContentType |
When I execute the following Fusion code:
"""fusion
include: resource://Neos.Fusion/Private/Fusion/Root.fusion
include: resource://Neos.Neos/Private/Fusion/Root.fusion
prototype(Neos.Neos:Test.ContentType) < prototype(Neos.Fusion:Value) {
value = ${node.identifier + ' (' + node.nodeType.name + ') '}
}
test = Neos.Neos:ContentCollection {
nodePath = 'main'
}
"""
Then I expect the following Fusion rendering result as HTML:
"""
<div class="neos-contentcollection">content1 (Neos.Neos:Test.ContentType) content2 (Neos.Neos:Test.ContentType) </div>
"""

0 comments on commit 821322b

Please sign in to comment.