Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUGFIX: 4909 fusion plugins and tests #5304

Draft
wants to merge 8 commits into
base: 9.0
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ public function theFollowingCreateNodeAggregateWithNodeCommandsAreExecuted(Table
if (isset($row['tetheredDescendantNodeAggregateIds'])) {
$command = $command->withTetheredDescendantNodeAggregateIds(NodeAggregateIdsByNodePaths::fromJsonString($row['tetheredDescendantNodeAggregateIds']));
}
if (isset($row['nodeName'])) {
if (!empty($row['nodeName'])) {
$command = $command->withNodeName(NodeName::fromString($row['nodeName']));
}
$this->currentContentRepository->handle($command);
Expand Down
4 changes: 2 additions & 2 deletions Neos.Fusion/Classes/Core/Runtime.php
Original file line number Diff line number Diff line change
Expand Up @@ -967,7 +967,7 @@ protected function throwExceptionForUnrenderablePathIfNeeded($fusionPath, $fusio
private function withSimulatedLegacyControllerContext(\Closure $renderer): ResponseInterface|StreamInterface
{
if ($this->legacyActionResponseForCurrentRendering !== null) {
throw new Exception('Recursion detected in `Runtime::renderResponse`. This entry point is only allowed to be invoked once per rendering.', 1706993940);
throw new Exception('Recursion detected in `Runtime::renderEntryPathWithContext`. This entry point is only allowed to be invoked once per rendering.', 1706993940);
}
$this->legacyActionResponseForCurrentRendering = new ActionResponse();

Expand Down Expand Up @@ -1017,7 +1017,7 @@ public function getControllerContext(): LegacyFusionControllerContext
// legacy controller context layer
$actionRequest = $this->fusionGlobals->get('request');
if ($this->legacyActionResponseForCurrentRendering === null || !$actionRequest instanceof ActionRequest) {
throw new Exception(sprintf('Fusions simulated legacy controller context is only available inside `Runtime::renderResponse` and when the Fusion global "request" is an ActionRequest.'), 1706458355);
throw new Exception(sprintf('Fusions simulated legacy controller context is only available inside `Runtime::renderEntryPathWithContext` and when the Fusion global "request" is an ActionRequest.'), 1706458355);
}

return new LegacyFusionControllerContext(
Expand Down
12 changes: 12 additions & 0 deletions Neos.Neos/Classes/Domain/Service/FusionService.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@
*/
protected $fusionConfigurationCache;

private ?FusionSourceCodeCollection $additionalFusionSourceCode = null;

/**
* @deprecated fixme!!!
*/
public function unsafeSetAdditionalFusionSourceCodeToThisSingleton(string $additionalFusionSourceCode)

Check failure on line 52 in Neos.Neos/Classes/Domain/Service/FusionService.php

View workflow job for this annotation

GitHub Actions / PHP 8.2 Test linting-unit-functionaltests-mysql (deps: highest)

Method Neos\Neos\Domain\Service\FusionService::unsafeSetAdditionalFusionSourceCodeToThisSingleton() has no return type specified.

Check failure on line 52 in Neos.Neos/Classes/Domain/Service/FusionService.php

View workflow job for this annotation

GitHub Actions / PHP 8.3 Test linting-unit-functionaltests-mysql (deps: highest)

Method Neos\Neos\Domain\Service\FusionService::unsafeSetAdditionalFusionSourceCodeToThisSingleton() has no return type specified.
{
$this->additionalFusionSourceCode = FusionSourceCodeCollection::fromString($additionalFusionSourceCode);
}

public function createFusionConfigurationFromSite(Site $site): FusionConfiguration
{
return $this->fusionConfigurationCache->cacheFusionConfigurationBySite($site, function () use ($site) {
Expand All @@ -56,6 +66,8 @@
)
->union(
FusionSourceCodeCollection::tryFromPackageRootFusion($siteResourcesPackageKey)
)->union(
$this->additionalFusionSourceCode ?? FusionSourceCodeCollection::empty()
)
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,15 @@ public function resolveWithParameters(array &$routeValues, RouteParameters $para
$currentRequestSiteDetectionResult = SiteDetectionResult::fromRouteParameters($parameters);

$nodeAddress = $routeValues[$this->name];

if (is_string($nodeAddress)) {
try {
$nodeAddress = NodeAddress::fromJsonString($nodeAddress);
} catch (\InvalidArgumentException) {
return false;
}
}

if (!$nodeAddress instanceof NodeAddress) {
return false;
}
Expand Down
130 changes: 130 additions & 0 deletions Neos.Neos/Tests/Behavior/Features/Bootstrap/DispatcherTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<?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.
*/

use Behat\Gherkin\Node\PyStringNode;
use GuzzleHttp\Psr7\Message;
use Neos\ContentRepository\TestSuite\Behavior\Features\Bootstrap\CRTestSuiteRuntimeVariables;
use Neos\Fusion\Core\Cache\ContentCache;
use Neos\Neos\Domain\Service\FusionService;
use org\bovigo\vfs\vfsStream;
use PHPUnit\Framework\Assert;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestFactoryInterface;

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

private ResponseInterface|null $response = null;

/**
* @template T of object
* @param class-string<T> $className
*
* @return T
*/
abstract private function getObject(string $className): object;

/**
* @BeforeScenario
*/
public function setupDispatcherTest(): void
{
$this->getObject(ContentCache::class)->flush();
$this->response = null;
}

/**
* @When I have the following Fusion file :fileName:
*/
public function iHaveTheFollowingFusionFile(PyStringNode $fusionCode, string $fileName)
{
if (!str_starts_with($fileName, 'vfs://fusion/')) {
throw new \InvalidArgumentException('Fusion file name must be virtual.');
}
vfsStream::setup('fusion');
file_put_contents($fileName, $fusionCode->getRaw());
}

/**
* @When the sites Fusion code is:
*/
public function iHaveTheFollowingFusionCodeForTheSite(PyStringNode $fusionCode)
{
$this->getObject(
FusionService::class
)->unsafeSetAdditionalFusionSourceCodeToThisSingleton(
$fusionCode->getRaw()
);
// $fakeFusionService = new class ($original) extends \Neos\Neos\Domain\Service\FusionService
// {
// public function __construct(
// private \Neos\Neos\Domain\Service\FusionService $original,
// private \Neos\Fusion\Core\FusionSourceCode $additionalFusion
// ) {
// }
// public function createFusionConfigurationFromSite(\Neos\Neos\Domain\Model\Site $site): \Neos\Fusion\Core\FusionConfiguration
// {
// $this->original->createFusionConfigurationFromSite($site)-> ... doest work
// }
// };
}

/**
* @When I declare the following controller :fullyQualifiedClassName:
*/
public function iDeclareTheFollowingController(string $fullyQualifiedClassName, PyStringNode $expectedResult): void
{
eval($expectedResult->getRaw());

$controllerInstance = new ('\\' . $fullyQualifiedClassName)();

if ($controllerInstance instanceof \Neos\Flow\Mvc\Controller\ActionController) {
// run flow property injection code of parent class ActionController not ActionController_Original manually as the extended classes is not proxied and doesnt call $this->Flow_Proxy_injectProperties();
$ref = new \ReflectionClass(get_parent_class($controllerInstance));
$method = $ref->getMethod('Flow_Proxy_injectProperties');
$method->invoke($controllerInstance);
}

$objectManager = $this->getObject(\Neos\Flow\ObjectManagement\ObjectManager::class);
$objects = \Neos\Utility\ObjectAccess::getProperty($objectManager, 'objects', true);
$objects[get_class($controllerInstance)]['i'] = $controllerInstance;
$objects[get_class($controllerInstance)]['l'] = strtolower(get_class($controllerInstance));
$objectManager->setObjects($objects);
}

/**
* @When I dispatch the following request :requestUri
*/
public function iDispatchTheFollowingRequest(string $requestUri)
{
$httpRequest = $this->getObject(ServerRequestFactoryInterface::class)->createServerRequest('GET', $requestUri);

$this->response = $this->getObject(\Neos\Flow\Http\Middleware\MiddlewaresChain::class)->handle(
$httpRequest
);
}

/**
* @Then I expect the following response:
*/
public function iExpectTheFollowingResponse(PyStringNode $expectedResult): void
{
Assert::assertNotNull($this->response);
Assert::assertEquals($expectedResult->getRaw(), str_replace("\r\n", "\n", Message::toString($this->response->withoutHeader('Content-Length'))));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class FeatureContext implements BehatContext
use CRBehavioralTestsSubjectProvider;
use RoutingTrait;
use MigrationsTrait;
use DispatcherTrait;
use FusionTrait;

use ContentCacheTrait;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
@flowEntities
Feature: Test the default Fusion rendering for a request
Background:
Given using no content dimensions
And using the following node types:
"""yaml
'Neos.ContentRepository:Root': {}
'Neos.Neos:ContentCollection': {}
'Neos.Neos:Content': {}
'Neos.Neos:Sites':
superTypes:
'Neos.ContentRepository:Root': true
'Neos.Neos:Document':
properties:
title:
type: string
uriPathSegment:
type: string
'Neos.Neos:Site':
superTypes:
'Neos.Neos:Document': true
childNodes:
main:
type: 'Neos.Neos:ContentCollection'
'Neos.Neos:Test.DocumentType':
superTypes:
'Neos.Neos:Document': true
childNodes:
main:
type: 'Neos.Neos:ContentCollection'
'Neos.Neos:Test.ContentType':
superTypes:
'Neos.Neos:Content': true
properties:
text:
type: string
"""
And using identifier "default", I define a content repository
And I am in content repository "default"
When the command CreateRootWorkspace is executed with payload:
| Key | Value |
| workspaceName | "live" |
| newContentStreamId | "cs-identifier" |
And I am in workspace "live" and dimension space point {}
And the command CreateRootNodeAggregateWithNode is executed with payload:
| Key | Value |
| nodeAggregateId | "root" |
| nodeTypeName | "Neos.Neos:Sites" |
And the following CreateNodeAggregateWithNode commands are executed:
| nodeAggregateId | parentNodeAggregateId | nodeTypeName | initialPropertyValues | tetheredDescendantNodeAggregateIds | nodeName |
| a | root | Neos.Neos:Site | {"title": "Node a"} | {} | a |
| a1 | a | Neos.Neos:Test.DocumentType | {"uriPathSegment": "a1", "title": "Node a1"} | {"main": "a-tetherton" } | |
| a1a1 | a-tetherton | Neos.Neos:Test.ContentType | {"text": "my first text"} | {} | |
| a1a2 | a-tetherton | Neos.Neos:Test.ContentType | {"text": "my second text"} | {} | |
And A site exists for node name "a" and domain "http://localhost"
And the sites configuration is:
"""yaml
Neos:
Neos:
sites:
'a':
preset: default
uriPathSuffix: ''
contentDimensions:
resolver:
factoryClassName: Neos\Neos\FrontendRouting\DimensionResolution\Resolver\NoopResolverFactory
"""

Scenario: Default output
And the sites Fusion code is:
"""fusion
prototype(Neos.Neos:Test.DocumentType) < prototype(Neos.Neos:Page) {
body {
content = Neos.Fusion:Component {
renderer = afx`
{String.chr(10)}title: {node.properties.title}
{String.chr(10)}children: <Neos.Neos:ContentCollection nodePath='main' />
{String.chr(10)}
`
}
}
}
prototype(Neos.Neos:Test.ContentType) < prototype(Neos.Neos:ContentComponent) {
text = Neos.Neos:Editable {
property = 'text'
}

renderer = afx`
[{props.text}]
`
}
"""

When I dispatch the following request "/a1"
Then I expect the following response:
"""
HTTP/1.1 200 OK
Content-Type: text/html
X-Flow-Powered: Flow/dev Neos/dev

<!DOCTYPE html><html>
<!--
This website is powered by Neos, the Open Source Content Application Platform licensed under the GNU/GPL.
Neos is based on Flow, a powerful PHP application framework licensed under the MIT license.

More information and contribution opportunities at https://www.neos.io
-->
<head><meta charset="UTF-8" /><title>Node a1</title></head><body class>
title: Node a1
children: <div class="neos-contentcollection">[my first text][my second text]</div>
</body></html>
"""
Loading
Loading