From 232a0b5737646acfab8e7ae06bbf078c9e1f56b9 Mon Sep 17 00:00:00 2001
From: Christian Kuhn <lolli@schwarzbu.ch>
Date: Thu, 11 Feb 2021 15:27:39 +0100
Subject: [PATCH] [TASK] Split two functional tests into smaller parts

The EnhancerLinkGeneratorTest and EnhancerSiteGeneratorTest have
524 and 460 tests. Hack these into smaller parts using multiple
files to be better digestible by the test splitter script.

Resolves: #93490
Releases: master
Change-Id: I2b926ed85211d8e19b6509f642e10cf24a5b44cc
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/67732
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: core-ci <typo3@b13.com>
Tested-by: Jochen <rothjochen@gmail.com>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Jochen <rothjochen@gmail.com>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
---
 .../AbstractEnhancerLinkGeneratorTestCase.php |  192 +++
 .../DefaultExtbaseControllerTest.php          |  192 +++
 .../LocaleModifierTest.php                    |  176 +++
 .../PersistedAliasMapperTest.php              |  150 +++
 .../PersistedPatternMapperTest.php            |  171 +++
 .../EnhancerLinkGenerator/RouteTest.php       |  301 +++++
 .../StaticRangeMapperTest.php                 |  160 +++
 .../StaticValueMapperTest.php                 |  179 +++
 .../EnhancerLinkGeneratorTest.php             | 1177 ----------------
 .../AbstractEnhancerSiteRequestTest.php       |  224 ++++
 .../LocaleModifierTest.php                    |  167 +++
 .../PageTypeDecoratorTest.php                 |  141 ++
 .../PersistedAliasMapperTest.php              |  122 ++
 .../PersistedPatternMapperTest.php            |  122 ++
 .../EnhancerSiteRequest/RouteTest.php         |  504 +++++++
 .../StaticRangeMapperTest.php                 |  133 ++
 .../StaticValueMapperTest.php                 |  130 ++
 .../SiteHandling/EnhancerSiteRequestTest.php  | 1181 -----------------
 18 files changed, 3064 insertions(+), 2358 deletions(-)
 create mode 100644 typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGenerator/AbstractEnhancerLinkGeneratorTestCase.php
 create mode 100644 typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGenerator/DefaultExtbaseControllerTest.php
 create mode 100644 typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGenerator/LocaleModifierTest.php
 create mode 100644 typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGenerator/PersistedAliasMapperTest.php
 create mode 100644 typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGenerator/PersistedPatternMapperTest.php
 create mode 100644 typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGenerator/RouteTest.php
 create mode 100644 typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGenerator/StaticRangeMapperTest.php
 create mode 100644 typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGenerator/StaticValueMapperTest.php
 delete mode 100644 typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGeneratorTest.php
 create mode 100644 typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequest/AbstractEnhancerSiteRequestTest.php
 create mode 100644 typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequest/LocaleModifierTest.php
 create mode 100644 typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequest/PageTypeDecoratorTest.php
 create mode 100644 typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequest/PersistedAliasMapperTest.php
 create mode 100644 typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequest/PersistedPatternMapperTest.php
 create mode 100644 typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequest/RouteTest.php
 create mode 100644 typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequest/StaticRangeMapperTest.php
 create mode 100644 typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequest/StaticValueMapperTest.php
 delete mode 100644 typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequestTest.php

diff --git a/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGenerator/AbstractEnhancerLinkGeneratorTestCase.php b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGenerator/AbstractEnhancerLinkGeneratorTestCase.php
new file mode 100644
index 000000000000..c125fcf0924e
--- /dev/null
+++ b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGenerator/AbstractEnhancerLinkGeneratorTestCase.php
@@ -0,0 +1,192 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\EnhancerLinkGenerator;
+
+use TYPO3\CMS\Core\Core\Bootstrap;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\AbstractTestCase;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Builder;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\LanguageContext;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\TestSet;
+use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerFactory;
+use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerWriter;
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequestContext;
+
+/**
+ * Abstract test case
+ */
+abstract class AbstractEnhancerLinkGeneratorTestCase extends AbstractTestCase
+{
+    /**
+     * @var InternalRequestContext
+     */
+    protected $internalRequestContext;
+
+    public static function setUpBeforeClass(): void
+    {
+        parent::setUpBeforeClass();
+        static::initializeDatabaseSnapshot();
+    }
+
+    public static function tearDownAfterClass(): void
+    {
+        static::destroyDatabaseSnapshot();
+        parent::tearDownAfterClass();
+    }
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+
+        // these settings are forwarded to the frontend sub-request as well
+        $this->internalRequestContext = (new InternalRequestContext())
+            ->withGlobalSettings(['TYPO3_CONF_VARS' => static::TYPO3_CONF_VARS]);
+
+        $this->writeSiteConfiguration(
+            'acme-com',
+            $this->buildSiteConfiguration(1000, 'https://acme.com/'),
+            [
+                $this->buildDefaultLanguageConfiguration('EN', 'https://acme.us/'),
+                $this->buildLanguageConfiguration('FR', 'https://acme.fr/', ['EN']),
+                $this->buildLanguageConfiguration('FR-CA', 'https://acme.ca/', ['FR', 'EN']),
+            ]
+        );
+
+        $this->writeSiteConfiguration(
+            'archive-acme-com',
+            $this->buildSiteConfiguration(3000, 'https://archive.acme.com/'),
+            [
+                $this->buildDefaultLanguageConfiguration('EN', '/'),
+                $this->buildLanguageConfiguration('FR', 'https://archive.acme.com/fr/', ['EN']),
+                $this->buildLanguageConfiguration('FR-CA', 'https://archive.acme.com/ca/', ['FR', 'EN'])
+            ]
+        );
+
+        $this->withDatabaseSnapshot(function () {
+            $this->setUpDatabase();
+        });
+    }
+
+    protected function tearDown(): void
+    {
+        unset($this->internalRequestContext);
+        parent::tearDown();
+    }
+
+    protected function setUpDatabase()
+    {
+        $backendUser = $this->setUpBackendUserFromFixture(1);
+        Bootstrap::initializeLanguageObject();
+
+        $scenarioFile = __DIR__ . '/../Fixtures/SlugScenario.yaml';
+        $factory = DataHandlerFactory::fromYamlFile($scenarioFile);
+        $writer = DataHandlerWriter::withBackendUser($backendUser);
+        $writer->invokeFactory($factory);
+        static::failIfArrayIsNotEmpty(
+            $writer->getErrors()
+        );
+
+        $this->setUpFrontendRootPage(
+            1000,
+            [
+                'typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/LinkGenerator.typoscript',
+            ],
+            [
+                'title' => 'ACME Root',
+            ]
+        );
+    }
+
+    /**
+     * This test is re-used in various child classes
+     *
+     * @param TestSet $testSet
+     */
+    protected function pageTypeDecoratorIsApplied(TestSet $testSet): void
+    {
+        $builder = Builder::create();
+        $enhancerConfiguration = $builder->compileEnhancerConfiguration($testSet);
+        $pageTypeConfiguration = $builder->compilePageTypeConfiguration($testSet);
+        $additionalParameters = $builder->compileGenerateParameters($testSet);
+        /** @var LanguageContext $languageContext */
+        $languageContext = $testSet->getSingleApplicable(LanguageContext::class);
+        $targetLanguageId = $languageContext->getLanguageId();
+        $expectation = $builder->compileUrl($testSet);
+
+        $this->mergeSiteConfiguration('acme-com', [
+            'routeEnhancers' => [
+                'Enhancer' => $enhancerConfiguration,
+                'PageType' => $pageTypeConfiguration,
+            ]
+        ]);
+
+        $this->mergeSiteConfiguration('archive-acme-com', [
+            'routeEnhancers' => [
+                'Enhancer' => $enhancerConfiguration,
+                'PageType' => $pageTypeConfiguration,
+            ]
+        ]);
+
+        $response = $this->executeFrontendSubRequest(
+            (new InternalRequest('https://acme.us/'))
+                ->withPageId(1100)
+                ->withInstructions([
+                    $this->createTypoLinkUrlInstruction([
+                        'parameter' => $testSet->getTargetPageId(),
+                        'language' => $targetLanguageId,
+                        'additionalParams' => $additionalParameters,
+                        'forceAbsoluteUrl' => 1,
+                    ])
+                ]),
+            $this->internalRequestContext
+        );
+
+        self::assertStringStartsWith($expectation, (string)$response->getBody());
+    }
+
+    protected function assertGeneratedUriEquals(TestSet $testSet): void
+    {
+        $builder = Builder::create();
+        $enhancerConfiguration = $builder->compileEnhancerConfiguration($testSet);
+        $additionalParameters = $builder->compileGenerateParameters($testSet);
+        /** @var LanguageContext $languageContext */
+        $languageContext = $testSet->getSingleApplicable(LanguageContext::class);
+        $targetLanguageId = $languageContext->getLanguageId();
+        $expectation = $builder->compileUrl($testSet);
+
+        $this->mergeSiteConfiguration('acme-com', [
+            'routeEnhancers' => ['Enhancer' => $enhancerConfiguration]
+        ]);
+
+        $response = $this->executeFrontendSubRequest(
+            (new InternalRequest('https://acme.us/'))
+                ->withPageId(1100)
+                ->withInstructions([
+                    $this->createTypoLinkUrlInstruction([
+                        'parameter' => $testSet->getTargetPageId(),
+                        'language' => $targetLanguageId,
+                        'additionalParams' => $additionalParameters,
+                        'forceAbsoluteUrl' => 1,
+                    ])
+                ]),
+            $this->internalRequestContext
+        );
+
+        self::assertStringStartsWith($expectation, (string)$response->getBody());
+    }
+}
diff --git a/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGenerator/DefaultExtbaseControllerTest.php b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGenerator/DefaultExtbaseControllerTest.php
new file mode 100644
index 000000000000..80e7088be7e9
--- /dev/null
+++ b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGenerator/DefaultExtbaseControllerTest.php
@@ -0,0 +1,192 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\EnhancerLinkGenerator;
+
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
+
+/**
+ * Test case
+ */
+class DefaultExtbaseControllerTest extends AbstractEnhancerLinkGeneratorTestCase
+{
+    /**
+     * @return array
+     */
+    public function defaultExtbaseControllerActionNamesAreAppliedWithAdditionalNonMappedQueryArgumentsDataProvider(): array
+    {
+        return [
+            '*::*' => [
+                '&tx_testing_link[value]=1&tx_testing_link[excludedValue]=random',
+                'https://acme.us/welcome/link/index/one?tx_testing_link%5BexcludedValue%5D=random'
+            ],
+            '*::list' => [
+                '&tx_testing_link[action]=list&tx_testing_link[value]=1&tx_testing_link[excludedValue]=random',
+                'https://acme.us/welcome/link/list/one?tx_testing_link%5BexcludedValue%5D=random'
+            ],
+            'Link::*' => [
+                // correctly falling back to defaultController here
+                '&tx_testing_link[controller]=Link&tx_testing_link[value]=1&tx_testing_link[excludedValue]=random',
+                'https://acme.us/welcome/link/index/one?tx_testing_link%5BexcludedValue%5D=random'
+            ],
+            'Page::*' => [
+                // correctly falling back to defaultController here
+                '&tx_testing_link[controller]=Page&tx_testing_link[value]=1&tx_testing_link[excludedValue]=random',
+                'https://acme.us/welcome/link/index/one?tx_testing_link%5BexcludedValue%5D=random'
+            ],
+            'Page::show' => [
+                '&tx_testing_link[controller]=Page&tx_testing_link[action]=show&tx_testing_link[value]=1&tx_testing_link[excludedValue]=random',
+                'https://acme.us/welcome/page/show/one?tx_testing_link%5BexcludedValue%5D=random'
+            ],
+        ];
+    }
+
+    /**
+     * Tests whether ExtbasePluginEnhancer applies `defaultController` values correctly but keeps additional Query Parameters.
+     *
+     * @param string $additionalParameters
+     * @param string $expectation
+     * @test
+     * @dataProvider defaultExtbaseControllerActionNamesAreAppliedWithAdditionalNonMappedQueryArgumentsDataProvider
+     */
+    public function defaultExtbaseControllerActionNamesAreAppliedWithAdditionalNonMappedQueryArguments(string $additionalParameters, string $expectation)
+    {
+        $targetLanguageId = 0;
+        $this->mergeSiteConfiguration('acme-com', [
+            'routeEnhancers' => [
+                'Enhancer' => [
+                    'type' => 'Extbase',
+                    'routes' => [
+                        ['routePath' => '/link/index/{value}', '_controller' => 'Link::index'],
+                        ['routePath' => '/link/list/{value}',  '_controller' => 'Link::list'],
+                        ['routePath' => '/page/show/{value}', '_controller' => 'Page::show'],
+                    ],
+                    'defaultController' => 'Link::index',
+                    'extension' => 'testing',
+                    'plugin' => 'link',
+                    'aspects' => [
+                        'value' => [
+                            'type' => 'StaticValueMapper',
+                            'map' => [
+                                'one' => 1,
+                            ],
+                        ],
+                    ],
+                ]
+            ]
+        ]);
+
+        $response = $this->executeFrontendSubRequest(
+            (new InternalRequest('https://acme.us/'))
+                ->withPageId(1100)
+                ->withInstructions([
+                    $this->createTypoLinkUrlInstruction([
+                        'parameter' => 1100,
+                        'language' => $targetLanguageId,
+                        'additionalParams' => $additionalParameters,
+                        'forceAbsoluteUrl' => 1,
+                    ])
+                ]),
+            $this->internalRequestContext
+        );
+
+        self::assertSame($expectation, (string)$response->getBody());
+    }
+
+    /**
+     * @return array
+     */
+    public function defaultExtbaseControllerActionNamesAreAppliedDataProvider(): array
+    {
+        return [
+            '*::*' => [
+                '&tx_testing_link[value]=1',
+                'https://acme.us/welcome/link/index/one'
+            ],
+            '*::list' => [
+                '&tx_testing_link[action]=list&tx_testing_link[value]=1',
+                'https://acme.us/welcome/link/list/one'
+            ],
+            'Link::*' => [
+                // correctly falling back to defaultController here
+                '&tx_testing_link[controller]=Link&tx_testing_link[value]=1',
+                'https://acme.us/welcome/link/index/one'
+            ],
+            'Page::*' => [
+                // correctly falling back to defaultController here
+                '&tx_testing_link[controller]=Page&tx_testing_link[value]=1',
+                'https://acme.us/welcome/link/index/one'
+            ],
+            'Page::show' => [
+                '&tx_testing_link[controller]=Page&tx_testing_link[action]=show&tx_testing_link[value]=1',
+                'https://acme.us/welcome/page/show/one'
+            ],
+        ];
+    }
+
+    /**
+     * Tests whether ExtbasePluginEnhancer applies `defaultController` values correctly.
+     *
+     * @param string $additionalParameters
+     * @param string $expectation
+     * @test
+     * @dataProvider defaultExtbaseControllerActionNamesAreAppliedDataProvider
+     */
+    public function defaultExtbaseControllerActionNamesAreApplied(string $additionalParameters, string $expectation)
+    {
+        $targetLanguageId = 0;
+        $this->mergeSiteConfiguration('acme-com', [
+            'routeEnhancers' => [
+                'Enhancer' => [
+                    'type' => 'Extbase',
+                    'routes' => [
+                        ['routePath' => '/link/index/{value}', '_controller' => 'Link::index'],
+                        ['routePath' => '/link/list/{value}',  '_controller' => 'Link::list'],
+                        ['routePath' => '/page/show/{value}', '_controller' => 'Page::show'],
+                    ],
+                    'defaultController' => 'Link::index',
+                    'extension' => 'testing',
+                    'plugin' => 'link',
+                    'aspects' => [
+                        'value' => [
+                            'type' => 'StaticValueMapper',
+                            'map' => [
+                                'one' => 1,
+                            ],
+                        ],
+                    ],
+                ]
+            ]
+        ]);
+
+        $response = $this->executeFrontendSubRequest(
+            (new InternalRequest('https://acme.us/'))
+                ->withPageId(1100)
+                ->withInstructions([
+                    $this->createTypoLinkUrlInstruction([
+                        'parameter' => 1100,
+                        'language' => $targetLanguageId,
+                        'additionalParams' => $additionalParameters,
+                        'forceAbsoluteUrl' => 1,
+                    ])
+                ]),
+            $this->internalRequestContext
+        );
+
+        self::assertSame($expectation, (string)$response->getBody());
+    }
+}
diff --git a/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGenerator/LocaleModifierTest.php b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGenerator/LocaleModifierTest.php
new file mode 100644
index 000000000000..d0461f8abc02
--- /dev/null
+++ b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGenerator/LocaleModifierTest.php
@@ -0,0 +1,176 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\EnhancerLinkGenerator;
+
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\AspectDeclaration;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Builder;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\LanguageContext;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Permutation;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\TestSet;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariableItem;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Variables;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariableValue;
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
+
+/**
+ * Test case
+ */
+class LocaleModifierTest extends AbstractEnhancerLinkGeneratorTestCase
+{
+    /**
+     * @param string|TestSet|null $parentSet
+     * @return array
+     */
+    public function localeModifierDataProvider($parentSet = null): array
+    {
+        $builder = Builder::create();
+        // variables (applied when invoking expectations)
+        $variables = Variables::create()->define([
+            'value' => 100,
+            'routePrefix' => '{enhance_name}',
+            'aspectName' => 'enhance_name',
+        ]);
+        return Permutation::create($variables)
+            ->withTargets(
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(0))
+                    ->withTargetPageId(1100)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://acme.us/welcome/enhance/[[value]][[pathSuffix]]?cHash=',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    ),
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(1))
+                    ->withTargetPageId(1100)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://acme.fr/bienvenue/augmenter/[[value]][[pathSuffix]]?cHash=',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    ),
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(0))
+                    ->withTargetPageId(3000)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://archive.acme.com/enhance/[[value]][[pathSuffix]]?cHash=',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    ),
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(1))
+                    ->withTargetPageId(3000)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://archive.acme.com/fr/augmenter/[[value]][[pathSuffix]]?cHash=',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    )
+            )
+            ->withApplicableItems($builder->declareEnhancers())
+            ->withApplicableSet(
+                AspectDeclaration::create('LocaleModifier')->withConfiguration([
+                    VariableItem::create('aspectName', [
+                        'type' => 'LocaleModifier',
+                        'default' => 'enhance',
+                        'localeMap' => [
+                            [
+                                'locale' => 'fr_FR',
+                                'value' => 'augmenter'
+                            ]
+                        ],
+                    ])
+                ])
+            )
+            ->permute()
+            ->getTargetsForDataProvider();
+    }
+
+    /**
+     * @param TestSet $testSet
+     * @test
+     * @dataProvider localeModifierDataProvider
+     */
+    public function localeModifierIsApplied(TestSet $testSet): void
+    {
+        $builder = Builder::create();
+        $enhancerConfiguration = $builder->compileEnhancerConfiguration($testSet);
+        $additionalParameters = $builder->compileGenerateParameters($testSet);
+        /** @var LanguageContext $languageContext */
+        $languageContext = $testSet->getSingleApplicable(LanguageContext::class);
+        $targetLanguageId = $languageContext->getLanguageId();
+        $expectation = $builder->compileUrl($testSet);
+
+        $this->mergeSiteConfiguration('acme-com', [
+            'routeEnhancers' => ['Enhancer' => $enhancerConfiguration]
+        ]);
+        $this->mergeSiteConfiguration('archive-acme-com', [
+            'routeEnhancers' => ['Enhancer' => $enhancerConfiguration]
+        ]);
+
+        $response = $this->executeFrontendSubRequest(
+            (new InternalRequest('https://acme.us/'))
+                ->withPageId(1100)
+                ->withInstructions([
+                    $this->createTypoLinkUrlInstruction([
+                        'parameter' => $testSet->getTargetPageId(),
+                        'language' => $targetLanguageId,
+                        'additionalParams' => $additionalParameters,
+                        'forceAbsoluteUrl' => 1,
+                    ])
+                ]),
+            $this->internalRequestContext
+        );
+
+        $body = (string)$response->getBody();
+        self::assertStringStartsWith($expectation, $body);
+    }
+
+    /**
+     * Combines the previous data provider for mappable aspects into one large
+     * data set that is permuted for several page type decorator instructions.
+     *
+     * @return array
+     */
+    public function pageTypeDecoratorIsAppliedDataProvider(): array
+    {
+        $testSets = [];
+        foreach (Builder::create()->declarePageTypes() as $pageTypeDeclaration) {
+            $testSet = TestSet::create()
+                ->withMergedApplicables($pageTypeDeclaration)
+                ->withVariables($pageTypeDeclaration->getVariables());
+            $testSets = array_merge(
+                $testSets,
+                $this->localeModifierDataProvider($testSet),
+            );
+        }
+        return $testSets;
+    }
+
+    /**
+     * @param TestSet $testSet
+     * @test
+     * @dataProvider pageTypeDecoratorIsAppliedDataProvider
+     */
+    public function pageTypeDecoratorIsApplied(TestSet $testSet): void
+    {
+        parent::pageTypeDecoratorIsApplied($testSet);
+    }
+}
diff --git a/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGenerator/PersistedAliasMapperTest.php b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGenerator/PersistedAliasMapperTest.php
new file mode 100644
index 000000000000..fbfc4dfaa4c9
--- /dev/null
+++ b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGenerator/PersistedAliasMapperTest.php
@@ -0,0 +1,150 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\EnhancerLinkGenerator;
+
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\AspectDeclaration;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Builder;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\LanguageContext;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Permutation;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\TestSet;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariableItem;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Variables;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariableValue;
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
+
+/**
+ * Test case
+ */
+class PersistedAliasMapperTest extends AbstractEnhancerLinkGeneratorTestCase
+{
+    /**
+     * @param string|TestSet|null $parentSet
+     * @return array
+     */
+    public function persistedAliasMapperDataProvider($parentSet = null): array
+    {
+        $builder = Builder::create();
+        // variables (applied when invoking expectations)
+        $variables = Variables::create()->define([
+            'value' => 1100,
+            'routePrefix' => 'enhance',
+            'aspectName' => 'value',
+        ]);
+        return Permutation::create($variables)
+            ->withTargets(
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(0))
+                    ->withTargetPageId(1100)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://acme.us/welcome/enhance/welcome[[pathSuffix]]',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    ),
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(1))
+                    ->withTargetPageId(1100)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://acme.fr/bienvenue/enhance/bienvenue[[pathSuffix]]',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    )
+            )
+            ->withApplicableItems($builder->declareEnhancers())
+            ->withApplicableSet(
+                AspectDeclaration::create('PersistedAliasMapper')->withConfiguration([
+                    VariableItem::create('aspectName', [
+                        'type' => 'PersistedAliasMapper',
+                        'tableName' => 'pages',
+                        'routeFieldName' => 'slug',
+                        'routeValuePrefix' => '/',
+                    ])
+                ])
+            )
+            ->permute()
+            ->getTargetsForDataProvider();
+    }
+
+    /**
+     * @param TestSet $testSet
+     * @test
+     * @dataProvider persistedAliasMapperDataProvider
+     */
+    public function persistedAliasMapperIsApplied(TestSet $testSet): void
+    {
+        $builder = Builder::create();
+        $enhancerConfiguration = $builder->compileEnhancerConfiguration($testSet);
+        $additionalParameters = $builder->compileGenerateParameters($testSet);
+        /** @var LanguageContext $languageContext */
+        $languageContext = $testSet->getSingleApplicable(LanguageContext::class);
+        $targetLanguageId = $languageContext->getLanguageId();
+        $expectation = $builder->compileUrl($testSet);
+
+        $this->mergeSiteConfiguration('acme-com', [
+            'routeEnhancers' => ['Enhancer' => $enhancerConfiguration]
+        ]);
+
+        $response = $this->executeFrontendSubRequest(
+            (new InternalRequest('https://acme.us/'))
+                ->withPageId(1100)
+                ->withInstructions([
+                    $this->createTypoLinkUrlInstruction([
+                        'parameter' => 1100,
+                        'language' => $targetLanguageId,
+                        'additionalParams' => $additionalParameters,
+                        'forceAbsoluteUrl' => 1,
+                    ])
+                ]),
+            $this->internalRequestContext
+        );
+
+        self::assertSame($expectation, (string)$response->getBody());
+    }
+
+    /**
+     * Combines the previous data provider for mappable aspects into one large
+     * data set that is permuted for several page type decorator instructions.
+     *
+     * @return array
+     */
+    public function pageTypeDecoratorIsAppliedDataProvider(): array
+    {
+        $testSets = [];
+        foreach (Builder::create()->declarePageTypes() as $pageTypeDeclaration) {
+            $testSet = TestSet::create()
+                ->withMergedApplicables($pageTypeDeclaration)
+                ->withVariables($pageTypeDeclaration->getVariables());
+            $testSets = array_merge(
+                $testSets,
+                $this->persistedAliasMapperDataProvider($testSet),
+            );
+        }
+        return $testSets;
+    }
+
+    /**
+     * @param TestSet $testSet
+     * @test
+     * @dataProvider pageTypeDecoratorIsAppliedDataProvider
+     */
+    public function pageTypeDecoratorIsApplied(TestSet $testSet): void
+    {
+        parent::pageTypeDecoratorIsApplied($testSet);
+    }
+}
diff --git a/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGenerator/PersistedPatternMapperTest.php b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGenerator/PersistedPatternMapperTest.php
new file mode 100644
index 000000000000..36912341ab44
--- /dev/null
+++ b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGenerator/PersistedPatternMapperTest.php
@@ -0,0 +1,171 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\EnhancerLinkGenerator;
+
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\AspectDeclaration;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Builder;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\LanguageContext;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Permutation;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\TestSet;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariableItem;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Variables;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariableValue;
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
+
+/**
+ * Test case
+ */
+class PersistedPatternMapperTest extends AbstractEnhancerLinkGeneratorTestCase
+{
+    /**
+     * @param string|TestSet|null $parentSet
+     * @return array
+     */
+    public function persistedPatternMapperDataProvider($parentSet = null): array
+    {
+        $builder = Builder::create();
+        // variables (applied when invoking expectations)
+        $variables = Variables::create()->define([
+            'value' => 1100,
+            'routePrefix' => 'enhance',
+            'aspectName' => 'value',
+        ]);
+        return Permutation::create($variables)
+            ->withTargets(
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(0))
+                    ->withTargetPageId(1100)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://acme.us/welcome/enhance/hello-and-welcome-[[value]][[pathSuffix]]',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    ),
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(1))
+                    ->withTargetPageId(1100)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://acme.fr/bienvenue/enhance/salut-et-bienvenue-[[value]][[pathSuffix]]',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    ),
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(0))
+                    ->withTargetPageId(3000)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://archive.acme.com/enhance/hello-and-welcome-[[value]][[pathSuffix]]',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    ),
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(1))
+                    ->withTargetPageId(3000)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://archive.acme.com/fr/enhance/salut-et-bienvenue-[[value]][[pathSuffix]]',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    )
+            )
+            ->withApplicableItems($builder->declareEnhancers())
+            ->withApplicableSet(
+                AspectDeclaration::create('PersistedPatternMapper')->withConfiguration([
+                    VariableItem::create('aspectName', [
+                        'type' => 'PersistedPatternMapper',
+                        'tableName' => 'pages',
+                        'routeFieldPattern' => '^(?P<subtitle>.+)-(?P<uid>\d+)$',
+                        'routeFieldResult' => '{subtitle}-{uid}',
+                    ])
+                ])
+            )
+            ->permute()
+            ->getTargetsForDataProvider();
+    }
+
+    /**
+     * @param TestSet $testSet
+     * @test
+     * @dataProvider persistedPatternMapperDataProvider
+     */
+    public function persistedPatternMapperIsApplied(TestSet $testSet): void
+    {
+        $builder = Builder::create();
+        $enhancerConfiguration = $builder->compileEnhancerConfiguration($testSet);
+        $additionalParameters = $builder->compileGenerateParameters($testSet);
+        /** @var LanguageContext $languageContext */
+        $languageContext = $testSet->getSingleApplicable(LanguageContext::class);
+        $targetLanguageId = $languageContext->getLanguageId();
+        $expectation = $builder->compileUrl($testSet);
+
+        $this->mergeSiteConfiguration('acme-com', [
+            'routeEnhancers' => ['Enhancer' => $enhancerConfiguration]
+        ]);
+        $this->mergeSiteConfiguration('archive-acme-com', [
+            'routeEnhancers' => ['Enhancer' => $enhancerConfiguration]
+        ]);
+
+        $response = $this->executeFrontendSubRequest(
+            (new InternalRequest('https://acme.us/'))
+                ->withPageId(1100)
+                ->withInstructions([
+                    $this->createTypoLinkUrlInstruction([
+                        'parameter' => $testSet->getTargetPageId(),
+                        'language' => $targetLanguageId,
+                        'additionalParams' => $additionalParameters,
+                        'forceAbsoluteUrl' => 1,
+                    ])
+                ]),
+            $this->internalRequestContext
+        );
+
+        self::assertSame($expectation, (string)$response->getBody());
+    }
+
+    /**
+     * Combines the previous data provider for mappable aspects into one large
+     * data set that is permuted for several page type decorator instructions.
+     *
+     * @return array
+     */
+    public function pageTypeDecoratorIsAppliedDataProvider(): array
+    {
+        $testSets = [];
+        foreach (Builder::create()->declarePageTypes() as $pageTypeDeclaration) {
+            $testSet = TestSet::create()
+                ->withMergedApplicables($pageTypeDeclaration)
+                ->withVariables($pageTypeDeclaration->getVariables());
+            $testSets = array_merge(
+                $testSets,
+                $this->persistedPatternMapperDataProvider($testSet),
+            );
+        }
+        return $testSets;
+    }
+
+    /**
+     * @param TestSet $testSet
+     * @test
+     * @dataProvider pageTypeDecoratorIsAppliedDataProvider
+     */
+    public function pageTypeDecoratorIsApplied(TestSet $testSet): void
+    {
+        parent::pageTypeDecoratorIsApplied($testSet);
+    }
+}
diff --git a/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGenerator/RouteTest.php b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGenerator/RouteTest.php
new file mode 100644
index 000000000000..1af46e842cf0
--- /dev/null
+++ b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGenerator/RouteTest.php
@@ -0,0 +1,301 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\EnhancerLinkGenerator;
+
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\ApplicableConjunction;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\AspectDeclaration;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Builder;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\EnhancerDeclaration;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\LanguageContext;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Permutation;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\TestSet;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariableItem;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Variables;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariablesContext;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariableValue;
+
+/**
+ * Test case
+ */
+class RouteTest extends AbstractEnhancerLinkGeneratorTestCase
+{
+    public function routeDefaultsForSingleParameterAreConsideredDataProvider($parentSet = null): array
+    {
+        $builder = Builder::create();
+        $enhancerDeclarations = $builder->declareEnhancers();
+        // variables (applied when invoking expectations)
+        $variables = Variables::create()->define([
+            'routePrefix' => 'enhance',
+            'aspectName' => 'value',
+        ]);
+        return Permutation::create($variables)
+            ->withTargets(
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(0))
+                    ->withTargetPageId(1100)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://acme.us/welcome/enhance[[uriValue]][[pathSuffix]]',
+                            Variables::create(['pathSuffix' => '', 'uriValue' => '/hundred'])
+                        )
+                    ),
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(1))
+                    ->withTargetPageId(1100)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://acme.fr/bienvenue/enhance[[uriValue]][[pathSuffix]]',
+                            Variables::create(['pathSuffix' => '', 'uriValue' => '/cent'])
+                        )
+                    )
+            )
+            ->withApplicableSet(
+                $enhancerDeclarations['Simple'],
+                // cannot use Plugin enhancer here - won't be used if no parameters for plugin namespace are given
+                // $enhancerDeclarations['Plugin']
+                //  ->withConfiguration(['routePath' => $routePath], true),
+                $enhancerDeclarations['Extbase']
+            )
+            ->withApplicableSet(
+                EnhancerDeclaration::create('defaults.value=100')->withConfiguration([
+                    'defaults' => ['value' => 100],
+                ])
+            )
+            ->withApplicableSet(
+                AspectDeclaration::create('StaticValueMapper')->withConfiguration([
+                    VariableItem::create('aspectName', [
+                        'type' => 'StaticValueMapper',
+                        'map' => [
+                            'hundred' => 100,
+                        ],
+                        'localeMap' => [
+                            [
+                                'locale' => 'fr_FR',
+                                'map' => [
+                                    'cent' => 100,
+                                ],
+                            ]
+                        ],
+                    ])
+                ])
+            )
+            ->withApplicableSet(
+                VariablesContext::create(Variables::create([
+                    'routeParameter' => '{value}',
+                    'uriValue' => '',
+                ])),
+                VariablesContext::create(Variables::create([
+                    'routeParameter' => '{!value}',
+                ]))
+            )
+            ->withApplicableSet(
+                VariablesContext::create(Variables::create([
+                    'value' => null,
+                ])),
+                VariablesContext::create(Variables::create([
+                    'value' => 100,
+                ]))
+            )
+            ->permute()
+            ->getTargetsForDataProvider();
+    }
+
+    /**
+     * @param TestSet $testSet
+     * @test
+     * @dataProvider routeDefaultsForSingleParameterAreConsideredDataProvider
+     */
+    public function routeDefaultsForSingleParameterAreConsidered(TestSet $testSet): void
+    {
+        $this->assertGeneratedUriEquals($testSet);
+    }
+
+    public function routeDefaultsForMultipleParametersAreConsideredDataProvider($parentSet = null): array
+    {
+        $builder = Builder::create();
+        $routePath = VariableValue::create('/[[routePrefix]]/[[routeParameter]]/{additional}');
+        $enhancerDeclarations = $builder->declareEnhancers();
+        // variables (applied when invoking expectations)
+        $variables = Variables::create()->define([
+            'routePrefix' => 'enhance',
+            'aspectName' => 'value',
+        ]);
+        return Permutation::create($variables)
+            ->withTargets(
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(0))
+                    ->withTargetPageId(1100)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://acme.us/welcome/enhance/hundred/20[[pathSuffix]]',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    ),
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(1))
+                    ->withTargetPageId(1100)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://acme.fr/bienvenue/enhance/cent/20[[pathSuffix]]',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    )
+            )
+            ->withApplicableSet(
+                $enhancerDeclarations['Simple']
+                    ->withConfiguration(['routePath' => $routePath], true)
+                    ->withGenerateParameters(['&additional=20'], true),
+                $enhancerDeclarations['Plugin']
+                    ->withConfiguration(['routePath' => $routePath], true)
+                    ->withGenerateParameters(['&testing[additional]=20'], true),
+                $enhancerDeclarations['Extbase']
+                    ->withConfiguration(['routes' => [0 => ['routePath' => $routePath]]], true)
+                    ->withGenerateParameters(['&tx_testing_link[additional]=20'], true)
+            )
+            ->withApplicableSet(
+                EnhancerDeclaration::create('defaults.value=100')->withConfiguration([
+                    'defaults' => ['value' => 100],
+                ])
+            )
+            ->withApplicableSet(
+                AspectDeclaration::create('StaticValueMapper')->withConfiguration([
+                    VariableItem::create('aspectName', [
+                        'type' => 'StaticValueMapper',
+                        'map' => [
+                            'hundred' => 100,
+                        ],
+                        'localeMap' => [
+                            [
+                                'locale' => 'fr_FR',
+                                'map' => [
+                                    'cent' => 100,
+                                ],
+                            ]
+                        ],
+                    ])
+                ])
+            )
+            ->withApplicableSet(
+                VariablesContext::create(Variables::create([
+                    'routeParameter' => '{value}',
+                ])),
+                VariablesContext::create(Variables::create([
+                    'routeParameter' => '{!value}',
+                ]))
+            )
+            ->withApplicableSet(
+                VariablesContext::create(Variables::create([
+                    'value' => null,
+                ])),
+                VariablesContext::create(Variables::create([
+                    'value' => 100,
+                ]))
+            )
+            ->permute()
+            ->getTargetsForDataProvider();
+    }
+
+    /**
+     * @param TestSet $testSet
+     * @test
+     * @dataProvider routeDefaultsForMultipleParametersAreConsideredDataProvider
+     */
+    public function routeDefaultsForMultipleParametersAreConsidered(TestSet $testSet): void
+    {
+        $this->assertGeneratedUriEquals($testSet);
+    }
+
+    public function routeRequirementsHavingAspectsAreConsideredDataProvider($parentSet = null): array
+    {
+        $builder = Builder::create();
+        // variables (applied when invoking expectations)
+        $variables = Variables::create()->define([
+            'routePrefix' => 'enhance',
+            'aspectName' => 'value',
+            'inArguments' => 'staticArguments' // either 'dynamicArguments' or 'staticArguments'
+        ]);
+        return Permutation::create($variables)
+            ->withTargets(
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(0))
+                    ->withTargetPageId(1100)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://acme.us/welcome/enhance/[[resolveValue]][[pathSuffix]]',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    )
+            )
+            ->withApplicableSet(
+                VariablesContext::create(Variables::create([
+                    'value' => 100,
+                    'resolveValue' => 'hundred',
+                ])),
+                VariablesContext::create(Variables::create([
+                    'value' => 1100100,
+                    'resolveValue' => 'hundred/binary',
+                ])),
+                ApplicableConjunction::create(
+                    VariablesContext::create(Variables::create([
+                        'value' => 100,
+                        'resolveValue' => 'hundred',
+                    ])),
+                    EnhancerDeclaration::create('requirements.value=/[a-z_/]+/')->withConfiguration([
+                        'requirements' => [
+                            'value' => '[a-z_/]+',
+                        ]
+                    ])
+                ),
+                ApplicableConjunction::create(
+                    VariablesContext::create(Variables::create([
+                        'value' => 1100100,
+                        'resolveValue' => 'hundred/binary',
+                    ])),
+                    EnhancerDeclaration::create('requirements.value=/[a-z_/]+/')->withConfiguration([
+                        'requirements' => [
+                            'value' => '[a-z_/]+',
+                        ]
+                    ])
+                )
+            )
+            ->withApplicableItems($builder->declareEnhancers())
+            ->withApplicableSet(
+                AspectDeclaration::create('StaticValueMapper')->withConfiguration([
+                    VariableItem::create('aspectName', [
+                        'type' => 'StaticValueMapper',
+                        'map' => [
+                            'hundred' => 100,
+                            'hundred/binary' => 1100100,
+                        ],
+                    ])
+                ])
+            )
+            ->permute()
+            ->getTargetsForDataProvider();
+    }
+
+    /**
+     * @param TestSet $testSet
+     * @test
+     * @dataProvider routeRequirementsHavingAspectsAreConsideredDataProvider
+     */
+    public function routeRequirementsHavingAspectsAreConsidered(TestSet $testSet): void
+    {
+        $this->assertGeneratedUriEquals($testSet);
+    }
+}
diff --git a/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGenerator/StaticRangeMapperTest.php b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGenerator/StaticRangeMapperTest.php
new file mode 100644
index 000000000000..c8c0e7231eed
--- /dev/null
+++ b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGenerator/StaticRangeMapperTest.php
@@ -0,0 +1,160 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\EnhancerLinkGenerator;
+
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\AspectDeclaration;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Builder;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\LanguageContext;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Permutation;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\TestSet;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariableItem;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Variables;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariablesContext;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariableValue;
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
+
+/**
+ * Test case
+ */
+class StaticRangeMapperTest extends AbstractEnhancerLinkGeneratorTestCase
+{
+    /**
+     * @param string|TestSet|null $parentSet
+     * @return array
+     */
+    public function staticRangeMapperDataProvider($parentSet = null): array
+    {
+        $variableContexts = array_map(
+            function ($value) {
+                return VariablesContext::create(
+                    Variables::create(['value' => $value])
+                );
+            },
+            range(10, 100, 30)
+        );
+
+        $builder = Builder::create();
+        // variables (applied when invoking expectations)
+        $variables = Variables::create()->define([
+            'value' => null, // defined via VariableContext
+            'routePrefix' => 'enhance',
+            'aspectName' => 'value',
+        ]);
+        return Permutation::create($variables)
+            ->withTargets(
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(0))
+                    ->withTargetPageId(1100)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://acme.us/welcome/enhance/[[value]][[pathSuffix]]',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    ),
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(1))
+                    ->withTargetPageId(1100)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://acme.fr/bienvenue/enhance/[[value]][[pathSuffix]]',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    )
+            )
+            ->withApplicableItems($variableContexts)
+            ->withApplicableItems($builder->declareEnhancers())
+            ->withApplicableSet(
+                AspectDeclaration::create('StaticRangeMapper')->withConfiguration([
+                    VariableItem::create('aspectName', [
+                        'type' => 'StaticRangeMapper',
+                        'start' => '1',
+                        'end' => '100',
+                    ])
+                ])
+            )
+            ->permute()
+            ->getTargetsForDataProvider();
+    }
+
+    /**
+     * @param TestSet $testSet
+     * @test
+     * @dataProvider staticRangeMapperDataProvider
+     */
+    public function staticRangeMapperIsApplied(TestSet $testSet): void
+    {
+        $builder = Builder::create();
+        $enhancerConfiguration = $builder->compileEnhancerConfiguration($testSet);
+        $additionalParameters = $builder->compileGenerateParameters($testSet);
+        /** @var LanguageContext $languageContext */
+        $languageContext = $testSet->getSingleApplicable(LanguageContext::class);
+        $targetLanguageId = $languageContext->getLanguageId();
+        $expectation = $builder->compileUrl($testSet);
+
+        $this->mergeSiteConfiguration('acme-com', [
+            'routeEnhancers' => ['Enhancer' => $enhancerConfiguration]
+        ]);
+
+        $response = $this->executeFrontendSubRequest(
+            (new InternalRequest('https://acme.us/'))
+                ->withPageId(1100)
+                ->withInstructions([
+                    $this->createTypoLinkUrlInstruction([
+                        'parameter' => $testSet->getTargetPageId(),
+                        'language' => $targetLanguageId,
+                        'additionalParams' => $additionalParameters,
+                        'forceAbsoluteUrl' => 1,
+                    ])
+                ]),
+            $this->internalRequestContext
+        );
+
+        self::assertStringStartsWith($expectation, (string)$response->getBody());
+    }
+
+    /**
+     * Combines the previous data provider for mappable aspects into one large
+     * data set that is permuted for several page type decorator instructions.
+     *
+     * @return array
+     */
+    public function pageTypeDecoratorIsAppliedDataProvider(): array
+    {
+        $testSets = [];
+        foreach (Builder::create()->declarePageTypes() as $pageTypeDeclaration) {
+            $testSet = TestSet::create()
+                ->withMergedApplicables($pageTypeDeclaration)
+                ->withVariables($pageTypeDeclaration->getVariables());
+            $testSets = array_merge(
+                $testSets,
+                $this->staticRangeMapperDataProvider($testSet)
+            );
+        }
+        return $testSets;
+    }
+
+    /**
+     * @param TestSet $testSet
+     * @test
+     * @dataProvider pageTypeDecoratorIsAppliedDataProvider
+     */
+    public function pageTypeDecoratorIsApplied(TestSet $testSet): void
+    {
+        parent::pageTypeDecoratorIsApplied($testSet);
+    }
+}
diff --git a/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGenerator/StaticValueMapperTest.php b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGenerator/StaticValueMapperTest.php
new file mode 100644
index 000000000000..954910c82b2c
--- /dev/null
+++ b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGenerator/StaticValueMapperTest.php
@@ -0,0 +1,179 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\EnhancerLinkGenerator;
+
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\AspectDeclaration;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Builder;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\LanguageContext;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Permutation;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\TestSet;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariableItem;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Variables;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariableValue;
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
+
+/**
+ * Test case
+ */
+class StaticValueMapperTest extends AbstractEnhancerLinkGeneratorTestCase
+{
+    /**
+     * @param string|TestSet|null $parentSet
+     * @return array
+     */
+    public function staticValueMapperDataProvider($parentSet = null): array
+    {
+        $builder = Builder::create();
+        // variables (applied when invoking expectations)
+        $variables = Variables::create()->define([
+            'value' => 100,
+            'routePrefix' => 'enhance',
+            'aspectName' => 'value',
+        ]);
+        return Permutation::create($variables)
+            ->withTargets(
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(0))
+                    ->withTargetPageId(1100)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://acme.us/welcome/enhance/hundred[[pathSuffix]]',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    ),
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(1))
+                    ->withTargetPageId(1100)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://acme.fr/bienvenue/enhance/cent[[pathSuffix]]',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    ),
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(0))
+                    ->withTargetPageId(3000)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://archive.acme.com/enhance/hundred[[pathSuffix]]',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    ),
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(1))
+                    ->withTargetPageId(3000)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://archive.acme.com/fr/enhance/cent[[pathSuffix]]',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    )
+            )
+            ->withApplicableItems($builder->declareEnhancers())
+            ->withApplicableSet(
+                AspectDeclaration::create('StaticValueMapper')->withConfiguration([
+                    VariableItem::create('aspectName', [
+                        'type' => 'StaticValueMapper',
+                        'map' => [
+                            'hundred' => 100,
+                        ],
+                        'localeMap' => [
+                            [
+                                'locale' => 'fr_FR',
+                                'map' => [
+                                    'cent' => 100,
+                                ],
+                            ]
+                        ],
+                    ])
+                ])
+            )
+            ->permute()
+            ->getTargetsForDataProvider();
+    }
+
+    /**
+     * @param TestSet $testSet
+     * @test
+     * @dataProvider staticValueMapperDataProvider
+     */
+    public function staticValueMapperIsApplied(TestSet $testSet): void
+    {
+        $builder = Builder::create();
+        $enhancerConfiguration = $builder->compileEnhancerConfiguration($testSet);
+        $additionalParameters = $builder->compileGenerateParameters($testSet);
+        /** @var LanguageContext $languageContext */
+        $languageContext = $testSet->getSingleApplicable(LanguageContext::class);
+        $targetLanguageId = $languageContext->getLanguageId();
+        $expectation = $builder->compileUrl($testSet);
+
+        $this->mergeSiteConfiguration('acme-com', [
+            'routeEnhancers' => ['Enhancer' => $enhancerConfiguration]
+        ]);
+        $this->mergeSiteConfiguration('archive-acme-com', [
+            'routeEnhancers' => ['Enhancer' => $enhancerConfiguration]
+        ]);
+
+        $response = $this->executeFrontendSubRequest(
+            (new InternalRequest('https://acme.us/'))
+                ->withPageId(1100)
+                ->withInstructions([
+                    $this->createTypoLinkUrlInstruction([
+                        'parameter' => $testSet->getTargetPageId(),
+                        'language' => $targetLanguageId,
+                        'additionalParams' => $additionalParameters,
+                        'forceAbsoluteUrl' => 1,
+                    ])
+                ]),
+            $this->internalRequestContext
+        );
+
+        self::assertStringStartsWith($expectation, (string)$response->getBody());
+    }
+
+    /**
+     * Combines the previous data provider for mappable aspects into one large
+     * data set that is permuted for several page type decorator instructions.
+     *
+     * @return array
+     */
+    public function pageTypeDecoratorIsAppliedDataProvider(): array
+    {
+        $testSets = [];
+        foreach (Builder::create()->declarePageTypes() as $pageTypeDeclaration) {
+            $testSet = TestSet::create()
+                ->withMergedApplicables($pageTypeDeclaration)
+                ->withVariables($pageTypeDeclaration->getVariables());
+            $testSets = array_merge(
+                $testSets,
+                $this->staticValueMapperDataProvider($testSet),
+            );
+        }
+        return $testSets;
+    }
+
+    /**
+     * @param TestSet $testSet
+     * @test
+     * @dataProvider pageTypeDecoratorIsAppliedDataProvider
+     */
+    public function pageTypeDecoratorIsApplied(TestSet $testSet): void
+    {
+        parent::pageTypeDecoratorIsApplied($testSet);
+    }
+}
diff --git a/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGeneratorTest.php b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGeneratorTest.php
deleted file mode 100644
index cd1d5541373d..000000000000
--- a/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGeneratorTest.php
+++ /dev/null
@@ -1,1177 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-
-namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling;
-
-use TYPO3\CMS\Core\Core\Bootstrap;
-use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\ApplicableConjunction;
-use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\AspectDeclaration;
-use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Builder;
-use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\EnhancerDeclaration;
-use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\LanguageContext;
-use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Permutation;
-use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\TestSet;
-use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariableItem;
-use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Variables;
-use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariablesContext;
-use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariableValue;
-use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerFactory;
-use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerWriter;
-use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
-use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequestContext;
-
-/**
- * Test case for frontend requests having site handling configured using enhancers.
- */
-class EnhancerLinkGeneratorTest extends AbstractTestCase
-{
-    /**
-     * @var InternalRequestContext
-     */
-    private $internalRequestContext;
-
-    public static function setUpBeforeClass(): void
-    {
-        parent::setUpBeforeClass();
-        static::initializeDatabaseSnapshot();
-    }
-
-    public static function tearDownAfterClass(): void
-    {
-        static::destroyDatabaseSnapshot();
-        parent::tearDownAfterClass();
-    }
-
-    protected function setUp(): void
-    {
-        parent::setUp();
-
-        // these settings are forwarded to the frontend sub-request as well
-        $this->internalRequestContext = (new InternalRequestContext())
-            ->withGlobalSettings(['TYPO3_CONF_VARS' => static::TYPO3_CONF_VARS]);
-
-        $this->writeSiteConfiguration(
-            'acme-com',
-            $this->buildSiteConfiguration(1000, 'https://acme.com/'),
-            [
-                $this->buildDefaultLanguageConfiguration('EN', 'https://acme.us/'),
-                $this->buildLanguageConfiguration('FR', 'https://acme.fr/', ['EN']),
-                $this->buildLanguageConfiguration('FR-CA', 'https://acme.ca/', ['FR', 'EN']),
-            ]
-        );
-
-        $this->writeSiteConfiguration(
-            'archive-acme-com',
-            $this->buildSiteConfiguration(3000, 'https://archive.acme.com/'),
-            [
-                $this->buildDefaultLanguageConfiguration('EN', '/'),
-                $this->buildLanguageConfiguration('FR', 'https://archive.acme.com/fr/', ['EN']),
-                $this->buildLanguageConfiguration('FR-CA', 'https://archive.acme.com/ca/', ['FR', 'EN'])
-            ]
-        );
-
-        $this->withDatabaseSnapshot(function () {
-            $this->setUpDatabase();
-        });
-    }
-
-    protected function setUpDatabase()
-    {
-        $backendUser = $this->setUpBackendUserFromFixture(1);
-        Bootstrap::initializeLanguageObject();
-
-        $scenarioFile = __DIR__ . '/Fixtures/SlugScenario.yaml';
-        $factory = DataHandlerFactory::fromYamlFile($scenarioFile);
-        $writer = DataHandlerWriter::withBackendUser($backendUser);
-        $writer->invokeFactory($factory);
-        static::failIfArrayIsNotEmpty(
-            $writer->getErrors()
-        );
-
-        $this->setUpFrontendRootPage(
-            1000,
-            [
-                'typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/LinkGenerator.typoscript',
-            ],
-            [
-                'title' => 'ACME Root',
-            ]
-        );
-    }
-
-    protected function tearDown(): void
-    {
-        unset($this->internalRequestContext);
-        parent::tearDown();
-    }
-
-    /**
-     * @param string|TestSet|null $parentSet
-     * @return array
-     */
-    public function localeModifierDataProvider($parentSet = null): array
-    {
-        $builder = Builder::create();
-        // variables (applied when invoking expectations)
-        $variables = Variables::create()->define([
-            'value' => 100,
-            'routePrefix' => '{enhance_name}',
-            'aspectName' => 'enhance_name',
-        ]);
-        return Permutation::create($variables)
-            ->withTargets(
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(0))
-                    ->withTargetPageId(1100)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://acme.us/welcome/enhance/[[value]][[pathSuffix]]?cHash=',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    ),
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(1))
-                    ->withTargetPageId(1100)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://acme.fr/bienvenue/augmenter/[[value]][[pathSuffix]]?cHash=',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    ),
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(0))
-                    ->withTargetPageId(3000)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://archive.acme.com/enhance/[[value]][[pathSuffix]]?cHash=',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    ),
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(1))
-                    ->withTargetPageId(3000)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://archive.acme.com/fr/augmenter/[[value]][[pathSuffix]]?cHash=',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    )
-            )
-            ->withApplicableItems($builder->declareEnhancers())
-            ->withApplicableSet(
-                AspectDeclaration::create('LocaleModifier')->withConfiguration([
-                    VariableItem::create('aspectName', [
-                        'type' => 'LocaleModifier',
-                        'default' => 'enhance',
-                        'localeMap' => [
-                            [
-                                'locale' => 'fr_FR',
-                                'value' => 'augmenter'
-                            ]
-                        ],
-                    ])
-                ])
-            )
-            ->permute()
-            ->getTargetsForDataProvider();
-    }
-
-    /**
-     * @param TestSet $testSet
-     *
-     * @test
-     * @dataProvider localeModifierDataProvider
-     */
-    public function localeModifierIsApplied(TestSet $testSet): void
-    {
-        $builder = Builder::create();
-        $enhancerConfiguration = $builder->compileEnhancerConfiguration($testSet);
-        $additionalParameters = $builder->compileGenerateParameters($testSet);
-        /** @var LanguageContext $languageContext */
-        $languageContext = $testSet->getSingleApplicable(LanguageContext::class);
-        $targetLanguageId = $languageContext->getLanguageId();
-        $expectation = $builder->compileUrl($testSet);
-
-        $this->mergeSiteConfiguration('acme-com', [
-            'routeEnhancers' => ['Enhancer' => $enhancerConfiguration]
-        ]);
-        $this->mergeSiteConfiguration('archive-acme-com', [
-            'routeEnhancers' => ['Enhancer' => $enhancerConfiguration]
-        ]);
-
-        $response = $this->executeFrontendSubRequest(
-            (new InternalRequest('https://acme.us/'))
-                ->withPageId(1100)
-                ->withInstructions([
-                    $this->createTypoLinkUrlInstruction([
-                        'parameter' => $testSet->getTargetPageId(),
-                        'language' => $targetLanguageId,
-                        'additionalParams' => $additionalParameters,
-                        'forceAbsoluteUrl' => 1,
-                    ])
-                ]),
-            $this->internalRequestContext
-        );
-
-        $body = (string)$response->getBody();
-        self::assertStringStartsWith($expectation, $body);
-    }
-
-    /**
-     * @param string|TestSet|null $parentSet
-     * @return array
-     */
-    public function persistedAliasMapperDataProvider($parentSet = null): array
-    {
-        $builder = Builder::create();
-        // variables (applied when invoking expectations)
-        $variables = Variables::create()->define([
-            'value' => 1100,
-            'routePrefix' => 'enhance',
-            'aspectName' => 'value',
-        ]);
-        return Permutation::create($variables)
-            ->withTargets(
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(0))
-                    ->withTargetPageId(1100)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://acme.us/welcome/enhance/welcome[[pathSuffix]]',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    ),
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(1))
-                    ->withTargetPageId(1100)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://acme.fr/bienvenue/enhance/bienvenue[[pathSuffix]]',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    )
-            )
-            ->withApplicableItems($builder->declareEnhancers())
-            ->withApplicableSet(
-                AspectDeclaration::create('PersistedAliasMapper')->withConfiguration([
-                    VariableItem::create('aspectName', [
-                        'type' => 'PersistedAliasMapper',
-                        'tableName' => 'pages',
-                        'routeFieldName' => 'slug',
-                        'routeValuePrefix' => '/',
-                    ])
-                ])
-            )
-            ->permute()
-            ->getTargetsForDataProvider();
-    }
-
-    /**
-     * @param TestSet $testSet
-     *
-     * @test
-     * @dataProvider persistedAliasMapperDataProvider
-     */
-    public function persistedAliasMapperIsApplied(TestSet $testSet): void
-    {
-        $builder = Builder::create();
-        $enhancerConfiguration = $builder->compileEnhancerConfiguration($testSet);
-        $additionalParameters = $builder->compileGenerateParameters($testSet);
-        /** @var LanguageContext $languageContext */
-        $languageContext = $testSet->getSingleApplicable(LanguageContext::class);
-        $targetLanguageId = $languageContext->getLanguageId();
-        $expectation = $builder->compileUrl($testSet);
-
-        $this->mergeSiteConfiguration('acme-com', [
-            'routeEnhancers' => ['Enhancer' => $enhancerConfiguration]
-        ]);
-
-        $response = $this->executeFrontendSubRequest(
-            (new InternalRequest('https://acme.us/'))
-                ->withPageId(1100)
-                ->withInstructions([
-                    $this->createTypoLinkUrlInstruction([
-                        'parameter' => 1100,
-                        'language' => $targetLanguageId,
-                        'additionalParams' => $additionalParameters,
-                        'forceAbsoluteUrl' => 1,
-                    ])
-                ]),
-            $this->internalRequestContext
-        );
-
-        self::assertSame($expectation, (string)$response->getBody());
-    }
-
-    /**
-     * @param string|TestSet|null $parentSet
-     * @return array
-     */
-    public function persistedPatternMapperDataProvider($parentSet = null): array
-    {
-        $builder = Builder::create();
-        // variables (applied when invoking expectations)
-        $variables = Variables::create()->define([
-            'value' => 1100,
-            'routePrefix' => 'enhance',
-            'aspectName' => 'value',
-        ]);
-        return Permutation::create($variables)
-            ->withTargets(
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(0))
-                    ->withTargetPageId(1100)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://acme.us/welcome/enhance/hello-and-welcome-[[value]][[pathSuffix]]',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    ),
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(1))
-                    ->withTargetPageId(1100)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://acme.fr/bienvenue/enhance/salut-et-bienvenue-[[value]][[pathSuffix]]',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    ),
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(0))
-                    ->withTargetPageId(3000)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://archive.acme.com/enhance/hello-and-welcome-[[value]][[pathSuffix]]',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    ),
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(1))
-                    ->withTargetPageId(3000)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://archive.acme.com/fr/enhance/salut-et-bienvenue-[[value]][[pathSuffix]]',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    )
-            )
-            ->withApplicableItems($builder->declareEnhancers())
-            ->withApplicableSet(
-                AspectDeclaration::create('PersistedPatternMapper')->withConfiguration([
-                    VariableItem::create('aspectName', [
-                        'type' => 'PersistedPatternMapper',
-                        'tableName' => 'pages',
-                        'routeFieldPattern' => '^(?P<subtitle>.+)-(?P<uid>\d+)$',
-                        'routeFieldResult' => '{subtitle}-{uid}',
-                    ])
-                ])
-            )
-            ->permute()
-            ->getTargetsForDataProvider();
-    }
-
-    /**
-     * @param TestSet $testSet
-     *
-     * @test
-     * @dataProvider persistedPatternMapperDataProvider
-     */
-    public function persistedPatternMapperIsApplied(TestSet $testSet): void
-    {
-        $builder = Builder::create();
-        $enhancerConfiguration = $builder->compileEnhancerConfiguration($testSet);
-        $additionalParameters = $builder->compileGenerateParameters($testSet);
-        /** @var LanguageContext $languageContext */
-        $languageContext = $testSet->getSingleApplicable(LanguageContext::class);
-        $targetLanguageId = $languageContext->getLanguageId();
-        $expectation = $builder->compileUrl($testSet);
-
-        $this->mergeSiteConfiguration('acme-com', [
-            'routeEnhancers' => ['Enhancer' => $enhancerConfiguration]
-        ]);
-        $this->mergeSiteConfiguration('archive-acme-com', [
-            'routeEnhancers' => ['Enhancer' => $enhancerConfiguration]
-        ]);
-
-        $response = $this->executeFrontendSubRequest(
-            (new InternalRequest('https://acme.us/'))
-                ->withPageId(1100)
-                ->withInstructions([
-                    $this->createTypoLinkUrlInstruction([
-                        'parameter' => $testSet->getTargetPageId(),
-                        'language' => $targetLanguageId,
-                        'additionalParams' => $additionalParameters,
-                        'forceAbsoluteUrl' => 1,
-                    ])
-                ]),
-            $this->internalRequestContext
-        );
-
-        self::assertSame($expectation, (string)$response->getBody());
-    }
-
-    /**
-     * @param string|TestSet|null $parentSet
-     * @return array
-     */
-    public function staticValueMapperDataProvider($parentSet = null): array
-    {
-        $builder = Builder::create();
-        // variables (applied when invoking expectations)
-        $variables = Variables::create()->define([
-            'value' => 100,
-            'routePrefix' => 'enhance',
-            'aspectName' => 'value',
-        ]);
-        return Permutation::create($variables)
-            ->withTargets(
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(0))
-                    ->withTargetPageId(1100)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://acme.us/welcome/enhance/hundred[[pathSuffix]]',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    ),
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(1))
-                    ->withTargetPageId(1100)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://acme.fr/bienvenue/enhance/cent[[pathSuffix]]',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    ),
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(0))
-                    ->withTargetPageId(3000)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://archive.acme.com/enhance/hundred[[pathSuffix]]',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    ),
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(1))
-                    ->withTargetPageId(3000)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://archive.acme.com/fr/enhance/cent[[pathSuffix]]',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    )
-            )
-            ->withApplicableItems($builder->declareEnhancers())
-            ->withApplicableSet(
-                AspectDeclaration::create('StaticValueMapper')->withConfiguration([
-                    VariableItem::create('aspectName', [
-                        'type' => 'StaticValueMapper',
-                        'map' => [
-                            'hundred' => 100,
-                        ],
-                        'localeMap' => [
-                            [
-                                'locale' => 'fr_FR',
-                                'map' => [
-                                    'cent' => 100,
-                                ],
-                            ]
-                        ],
-                    ])
-                ])
-            )
-            ->permute()
-            ->getTargetsForDataProvider();
-    }
-
-    /**
-     * @param TestSet $testSet
-     *
-     * @test
-     * @dataProvider staticValueMapperDataProvider
-     */
-    public function staticValueMapperIsApplied(TestSet $testSet): void
-    {
-        $builder = Builder::create();
-        $enhancerConfiguration = $builder->compileEnhancerConfiguration($testSet);
-        $additionalParameters = $builder->compileGenerateParameters($testSet);
-        /** @var LanguageContext $languageContext */
-        $languageContext = $testSet->getSingleApplicable(LanguageContext::class);
-        $targetLanguageId = $languageContext->getLanguageId();
-        $expectation = $builder->compileUrl($testSet);
-
-        $this->mergeSiteConfiguration('acme-com', [
-            'routeEnhancers' => ['Enhancer' => $enhancerConfiguration]
-        ]);
-        $this->mergeSiteConfiguration('archive-acme-com', [
-            'routeEnhancers' => ['Enhancer' => $enhancerConfiguration]
-        ]);
-
-        $response = $this->executeFrontendSubRequest(
-            (new InternalRequest('https://acme.us/'))
-                ->withPageId(1100)
-                ->withInstructions([
-                    $this->createTypoLinkUrlInstruction([
-                        'parameter' => $testSet->getTargetPageId(),
-                        'language' => $targetLanguageId,
-                        'additionalParams' => $additionalParameters,
-                        'forceAbsoluteUrl' => 1,
-                    ])
-                ]),
-            $this->internalRequestContext
-        );
-
-        self::assertStringStartsWith($expectation, (string)$response->getBody());
-    }
-
-    /**
-     * @param string|TestSet|null $parentSet
-     * @return array
-     */
-    public function staticRangeMapperDataProvider($parentSet = null): array
-    {
-        $variableContexts = array_map(
-            function ($value) {
-                return VariablesContext::create(
-                    Variables::create(['value' => $value])
-                );
-            },
-            range(10, 100, 30)
-        );
-
-        $builder = Builder::create();
-        // variables (applied when invoking expectations)
-        $variables = Variables::create()->define([
-            'value' => null, // defined via VariableContext
-            'routePrefix' => 'enhance',
-            'aspectName' => 'value',
-        ]);
-        return Permutation::create($variables)
-            ->withTargets(
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(0))
-                    ->withTargetPageId(1100)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://acme.us/welcome/enhance/[[value]][[pathSuffix]]',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    ),
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(1))
-                    ->withTargetPageId(1100)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://acme.fr/bienvenue/enhance/[[value]][[pathSuffix]]',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    )
-            )
-            ->withApplicableItems($variableContexts)
-            ->withApplicableItems($builder->declareEnhancers())
-            ->withApplicableSet(
-                AspectDeclaration::create('StaticRangeMapper')->withConfiguration([
-                    VariableItem::create('aspectName', [
-                        'type' => 'StaticRangeMapper',
-                        'start' => '1',
-                        'end' => '100',
-                    ])
-                ])
-            )
-            ->permute()
-            ->getTargetsForDataProvider();
-    }
-
-    /**
-     * @param TestSet $testSet
-     *
-     * @test
-     * @dataProvider staticRangeMapperDataProvider
-     */
-    public function staticRangeMapperIsApplied(TestSet $testSet): void
-    {
-        $builder = Builder::create();
-        $enhancerConfiguration = $builder->compileEnhancerConfiguration($testSet);
-        $additionalParameters = $builder->compileGenerateParameters($testSet);
-        /** @var LanguageContext $languageContext */
-        $languageContext = $testSet->getSingleApplicable(LanguageContext::class);
-        $targetLanguageId = $languageContext->getLanguageId();
-        $expectation = $builder->compileUrl($testSet);
-
-        $this->mergeSiteConfiguration('acme-com', [
-            'routeEnhancers' => ['Enhancer' => $enhancerConfiguration]
-        ]);
-
-        $response = $this->executeFrontendSubRequest(
-            (new InternalRequest('https://acme.us/'))
-                ->withPageId(1100)
-                ->withInstructions([
-                    $this->createTypoLinkUrlInstruction([
-                        'parameter' => $testSet->getTargetPageId(),
-                        'language' => $targetLanguageId,
-                        'additionalParams' => $additionalParameters,
-                        'forceAbsoluteUrl' => 1,
-                    ])
-                ]),
-            $this->internalRequestContext
-        );
-
-        self::assertStringStartsWith($expectation, (string)$response->getBody());
-    }
-
-    /**
-     * Combines all previous data providers for mappable aspects into one large
-     * data set that is permuted for several page type decorator instructions.
-     *
-     * @return array
-     */
-    public function pageTypeDecoratorIsAppliedDataProvider(): array
-    {
-        $testSets = [];
-        foreach (Builder::create()->declarePageTypes() as $pageTypeDeclaration) {
-            $testSet = TestSet::create()
-                ->withMergedApplicables($pageTypeDeclaration)
-                ->withVariables($pageTypeDeclaration->getVariables());
-            $testSets = array_merge(
-                $testSets,
-                $this->localeModifierDataProvider($testSet),
-                $this->persistedAliasMapperDataProvider($testSet),
-                $this->persistedPatternMapperDataProvider($testSet),
-                $this->staticValueMapperDataProvider($testSet),
-                $this->staticRangeMapperDataProvider($testSet)
-            );
-        }
-        return $testSets;
-    }
-
-    /**
-     * @param TestSet $testSet
-     *
-     * @test
-     * @dataProvider pageTypeDecoratorIsAppliedDataProvider
-     */
-    public function pageTypeDecoratorIsApplied(TestSet $testSet): void
-    {
-        $builder = Builder::create();
-        $enhancerConfiguration = $builder->compileEnhancerConfiguration($testSet);
-        $pageTypeConfiguration = $builder->compilePageTypeConfiguration($testSet);
-        $additionalParameters = $builder->compileGenerateParameters($testSet);
-        /** @var LanguageContext $languageContext */
-        $languageContext = $testSet->getSingleApplicable(LanguageContext::class);
-        $targetLanguageId = $languageContext->getLanguageId();
-        $expectation = $builder->compileUrl($testSet);
-
-        $this->mergeSiteConfiguration('acme-com', [
-            'routeEnhancers' => [
-                'Enhancer' => $enhancerConfiguration,
-                'PageType' => $pageTypeConfiguration,
-            ]
-        ]);
-
-        $this->mergeSiteConfiguration('archive-acme-com', [
-            'routeEnhancers' => [
-                'Enhancer' => $enhancerConfiguration,
-                'PageType' => $pageTypeConfiguration,
-            ]
-        ]);
-
-        $response = $this->executeFrontendSubRequest(
-            (new InternalRequest('https://acme.us/'))
-                ->withPageId(1100)
-                ->withInstructions([
-                    $this->createTypoLinkUrlInstruction([
-                        'parameter' => $testSet->getTargetPageId(),
-                        'language' => $targetLanguageId,
-                        'additionalParams' => $additionalParameters,
-                        'forceAbsoluteUrl' => 1,
-                    ])
-                ]),
-            $this->internalRequestContext
-        );
-
-        self::assertStringStartsWith($expectation, (string)$response->getBody());
-    }
-
-    public function routeDefaultsForSingleParameterAreConsideredDataProvider($parentSet = null): array
-    {
-        $builder = Builder::create();
-        $enhancerDeclarations = $builder->declareEnhancers();
-        // variables (applied when invoking expectations)
-        $variables = Variables::create()->define([
-            'routePrefix' => 'enhance',
-            'aspectName' => 'value',
-        ]);
-        return Permutation::create($variables)
-            ->withTargets(
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(0))
-                    ->withTargetPageId(1100)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://acme.us/welcome/enhance[[uriValue]][[pathSuffix]]',
-                            Variables::create(['pathSuffix' => '', 'uriValue' => '/hundred'])
-                        )
-                    ),
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(1))
-                    ->withTargetPageId(1100)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://acme.fr/bienvenue/enhance[[uriValue]][[pathSuffix]]',
-                            Variables::create(['pathSuffix' => '', 'uriValue' => '/cent'])
-                        )
-                    )
-            )
-            ->withApplicableSet(
-                $enhancerDeclarations['Simple'],
-                // cannot use Plugin enhancer here - won't be used if no parameters for plugin namespace are given
-                // $enhancerDeclarations['Plugin']
-                //  ->withConfiguration(['routePath' => $routePath], true),
-                $enhancerDeclarations['Extbase']
-            )
-            ->withApplicableSet(
-                EnhancerDeclaration::create('defaults.value=100')->withConfiguration([
-                    'defaults' => ['value' => 100],
-                ])
-            )
-            ->withApplicableSet(
-                AspectDeclaration::create('StaticValueMapper')->withConfiguration([
-                    VariableItem::create('aspectName', [
-                        'type' => 'StaticValueMapper',
-                        'map' => [
-                            'hundred' => 100,
-                        ],
-                        'localeMap' => [
-                            [
-                                'locale' => 'fr_FR',
-                                'map' => [
-                                    'cent' => 100,
-                                ],
-                            ]
-                        ],
-                    ])
-                ])
-            )
-            ->withApplicableSet(
-                VariablesContext::create(Variables::create([
-                    'routeParameter' => '{value}',
-                    'uriValue' => '',
-                ])),
-                VariablesContext::create(Variables::create([
-                    'routeParameter' => '{!value}',
-                ]))
-            )
-            ->withApplicableSet(
-                VariablesContext::create(Variables::create([
-                    'value' => null,
-                ])),
-                VariablesContext::create(Variables::create([
-                    'value' => 100,
-                ]))
-            )
-            ->permute()
-            ->getTargetsForDataProvider();
-    }
-
-    /**
-     * @param TestSet $testSet
-     *
-     * @test
-     * @dataProvider routeDefaultsForSingleParameterAreConsideredDataProvider
-     */
-    public function routeDefaultsForSingleParameterAreConsidered(TestSet $testSet): void
-    {
-        $this->assertGeneratedUriEquals($testSet);
-    }
-
-    public function routeDefaultsForMultipleParametersAreConsideredDataProvider($parentSet = null): array
-    {
-        $builder = Builder::create();
-        $routePath = VariableValue::create('/[[routePrefix]]/[[routeParameter]]/{additional}');
-        $enhancerDeclarations = $builder->declareEnhancers();
-        // variables (applied when invoking expectations)
-        $variables = Variables::create()->define([
-            'routePrefix' => 'enhance',
-            'aspectName' => 'value',
-        ]);
-        return Permutation::create($variables)
-            ->withTargets(
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(0))
-                    ->withTargetPageId(1100)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://acme.us/welcome/enhance/hundred/20[[pathSuffix]]',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    ),
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(1))
-                    ->withTargetPageId(1100)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://acme.fr/bienvenue/enhance/cent/20[[pathSuffix]]',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    )
-            )
-            ->withApplicableSet(
-                $enhancerDeclarations['Simple']
-                    ->withConfiguration(['routePath' => $routePath], true)
-                    ->withGenerateParameters(['&additional=20'], true),
-                $enhancerDeclarations['Plugin']
-                    ->withConfiguration(['routePath' => $routePath], true)
-                    ->withGenerateParameters(['&testing[additional]=20'], true),
-                $enhancerDeclarations['Extbase']
-                    ->withConfiguration(['routes' => [0 => ['routePath' => $routePath]]], true)
-                    ->withGenerateParameters(['&tx_testing_link[additional]=20'], true)
-            )
-            ->withApplicableSet(
-                EnhancerDeclaration::create('defaults.value=100')->withConfiguration([
-                    'defaults' => ['value' => 100],
-                ])
-            )
-            ->withApplicableSet(
-                AspectDeclaration::create('StaticValueMapper')->withConfiguration([
-                    VariableItem::create('aspectName', [
-                        'type' => 'StaticValueMapper',
-                        'map' => [
-                            'hundred' => 100,
-                        ],
-                        'localeMap' => [
-                            [
-                                'locale' => 'fr_FR',
-                                'map' => [
-                                    'cent' => 100,
-                                ],
-                            ]
-                        ],
-                    ])
-                ])
-            )
-            ->withApplicableSet(
-                VariablesContext::create(Variables::create([
-                    'routeParameter' => '{value}',
-                ])),
-                VariablesContext::create(Variables::create([
-                    'routeParameter' => '{!value}',
-                ]))
-            )
-            ->withApplicableSet(
-                VariablesContext::create(Variables::create([
-                    'value' => null,
-                ])),
-                VariablesContext::create(Variables::create([
-                    'value' => 100,
-                ]))
-            )
-            ->permute()
-            ->getTargetsForDataProvider();
-    }
-
-    /**
-     * @param TestSet $testSet
-     *
-     * @test
-     * @dataProvider routeDefaultsForMultipleParametersAreConsideredDataProvider
-     */
-    public function routeDefaultsForMultipleParametersAreConsidered(TestSet $testSet): void
-    {
-        $this->assertGeneratedUriEquals($testSet);
-    }
-
-    public function routeRequirementsHavingAspectsAreConsideredDataProvider($parentSet = null): array
-    {
-        $builder = Builder::create();
-        // variables (applied when invoking expectations)
-        $variables = Variables::create()->define([
-            'routePrefix' => 'enhance',
-            'aspectName' => 'value',
-            'inArguments' => 'staticArguments' // either 'dynamicArguments' or 'staticArguments'
-        ]);
-        return Permutation::create($variables)
-            ->withTargets(
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(0))
-                    ->withTargetPageId(1100)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://acme.us/welcome/enhance/[[resolveValue]][[pathSuffix]]',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    )
-            )
-            ->withApplicableSet(
-                VariablesContext::create(Variables::create([
-                    'value' => 100,
-                    'resolveValue' => 'hundred',
-                ])),
-                VariablesContext::create(Variables::create([
-                    'value' => 1100100,
-                    'resolveValue' => 'hundred/binary',
-                ])),
-                ApplicableConjunction::create(
-                    VariablesContext::create(Variables::create([
-                        'value' => 100,
-                        'resolveValue' => 'hundred',
-                    ])),
-                    EnhancerDeclaration::create('requirements.value=/[a-z_/]+/')->withConfiguration([
-                        'requirements' => [
-                            'value' => '[a-z_/]+',
-                        ]
-                    ])
-                ),
-                ApplicableConjunction::create(
-                    VariablesContext::create(Variables::create([
-                        'value' => 1100100,
-                        'resolveValue' => 'hundred/binary',
-                    ])),
-                    EnhancerDeclaration::create('requirements.value=/[a-z_/]+/')->withConfiguration([
-                        'requirements' => [
-                            'value' => '[a-z_/]+',
-                        ]
-                    ])
-                )
-            )
-            ->withApplicableItems($builder->declareEnhancers())
-            ->withApplicableSet(
-                AspectDeclaration::create('StaticValueMapper')->withConfiguration([
-                    VariableItem::create('aspectName', [
-                        'type' => 'StaticValueMapper',
-                        'map' => [
-                            'hundred' => 100,
-                            'hundred/binary' => 1100100,
-                        ],
-                    ])
-                ])
-            )
-            ->permute()
-            ->getTargetsForDataProvider();
-    }
-
-    /**
-     * @param TestSet $testSet
-     *
-     * @test
-     * @dataProvider routeRequirementsHavingAspectsAreConsideredDataProvider
-     */
-    public function routeRequirementsHavingAspectsAreConsidered(TestSet $testSet): void
-    {
-        $this->assertGeneratedUriEquals($testSet);
-    }
-
-    private function assertGeneratedUriEquals(TestSet $testSet): void
-    {
-        $builder = Builder::create();
-        $enhancerConfiguration = $builder->compileEnhancerConfiguration($testSet);
-        $additionalParameters = $builder->compileGenerateParameters($testSet);
-        /** @var LanguageContext $languageContext */
-        $languageContext = $testSet->getSingleApplicable(LanguageContext::class);
-        $targetLanguageId = $languageContext->getLanguageId();
-        $expectation = $builder->compileUrl($testSet);
-
-        $this->mergeSiteConfiguration('acme-com', [
-            'routeEnhancers' => ['Enhancer' => $enhancerConfiguration]
-        ]);
-
-        $response = $this->executeFrontendSubRequest(
-            (new InternalRequest('https://acme.us/'))
-                ->withPageId(1100)
-                ->withInstructions([
-                    $this->createTypoLinkUrlInstruction([
-                        'parameter' => $testSet->getTargetPageId(),
-                        'language' => $targetLanguageId,
-                        'additionalParams' => $additionalParameters,
-                        'forceAbsoluteUrl' => 1,
-                    ])
-                ]),
-            $this->internalRequestContext
-        );
-
-        self::assertStringStartsWith($expectation, (string)$response->getBody());
-    }
-
-    /**
-     * @return array
-     */
-    public function defaultExtbaseControllerActionNamesAreAppliedDataProvider(): array
-    {
-        return [
-            '*::*' => [
-                '&tx_testing_link[value]=1',
-                'https://acme.us/welcome/link/index/one'
-            ],
-            '*::list' => [
-                '&tx_testing_link[action]=list&tx_testing_link[value]=1',
-                'https://acme.us/welcome/link/list/one'
-            ],
-            'Link::*' => [
-                // correctly falling back to defaultController here
-                '&tx_testing_link[controller]=Link&tx_testing_link[value]=1',
-                'https://acme.us/welcome/link/index/one'
-            ],
-            'Page::*' => [
-                // correctly falling back to defaultController here
-                '&tx_testing_link[controller]=Page&tx_testing_link[value]=1',
-                'https://acme.us/welcome/link/index/one'
-            ],
-            'Page::show' => [
-                '&tx_testing_link[controller]=Page&tx_testing_link[action]=show&tx_testing_link[value]=1',
-                'https://acme.us/welcome/page/show/one'
-            ],
-        ];
-    }
-
-    /**
-     * Tests whether ExtbasePluginEnhancer applies `defaultController` values correctly.
-     *
-     * @param string $additionalParameters
-     * @param string $expectation
-     *
-     * @test
-     * @dataProvider defaultExtbaseControllerActionNamesAreAppliedDataProvider
-     */
-    public function defaultExtbaseControllerActionNamesAreApplied(string $additionalParameters, string $expectation)
-    {
-        $targetLanguageId = 0;
-        $this->mergeSiteConfiguration('acme-com', [
-            'routeEnhancers' => [
-                'Enhancer' => [
-                    'type' => 'Extbase',
-                    'routes' => [
-                        ['routePath' => '/link/index/{value}', '_controller' => 'Link::index'],
-                        ['routePath' => '/link/list/{value}',  '_controller' => 'Link::list'],
-                        ['routePath' => '/page/show/{value}', '_controller' => 'Page::show'],
-                    ],
-                    'defaultController' => 'Link::index',
-                    'extension' => 'testing',
-                    'plugin' => 'link',
-                    'aspects' => [
-                        'value' => [
-                            'type' => 'StaticValueMapper',
-                            'map' => [
-                                'one' => 1,
-                            ],
-                        ],
-                    ],
-                ]
-            ]
-        ]);
-
-        $response = $this->executeFrontendSubRequest(
-            (new InternalRequest('https://acme.us/'))
-                ->withPageId(1100)
-                ->withInstructions([
-                    $this->createTypoLinkUrlInstruction([
-                        'parameter' => 1100,
-                        'language' => $targetLanguageId,
-                        'additionalParams' => $additionalParameters,
-                        'forceAbsoluteUrl' => 1,
-                    ])
-                ]),
-            $this->internalRequestContext
-        );
-
-        self::assertSame($expectation, (string)$response->getBody());
-    }
-
-    /**
-     * @return array
-     */
-    public function defaultExtbaseControllerActionNamesAreAppliedWithAdditionalNonMappedQueryArgumentsDataProvider(): array
-    {
-        return [
-            '*::*' => [
-                '&tx_testing_link[value]=1&tx_testing_link[excludedValue]=random',
-                'https://acme.us/welcome/link/index/one?tx_testing_link%5BexcludedValue%5D=random'
-            ],
-            '*::list' => [
-                '&tx_testing_link[action]=list&tx_testing_link[value]=1&tx_testing_link[excludedValue]=random',
-                'https://acme.us/welcome/link/list/one?tx_testing_link%5BexcludedValue%5D=random'
-            ],
-            'Link::*' => [
-                // correctly falling back to defaultController here
-                '&tx_testing_link[controller]=Link&tx_testing_link[value]=1&tx_testing_link[excludedValue]=random',
-                'https://acme.us/welcome/link/index/one?tx_testing_link%5BexcludedValue%5D=random'
-            ],
-            'Page::*' => [
-                // correctly falling back to defaultController here
-                '&tx_testing_link[controller]=Page&tx_testing_link[value]=1&tx_testing_link[excludedValue]=random',
-                'https://acme.us/welcome/link/index/one?tx_testing_link%5BexcludedValue%5D=random'
-            ],
-            'Page::show' => [
-                '&tx_testing_link[controller]=Page&tx_testing_link[action]=show&tx_testing_link[value]=1&tx_testing_link[excludedValue]=random',
-                'https://acme.us/welcome/page/show/one?tx_testing_link%5BexcludedValue%5D=random'
-            ],
-        ];
-    }
-
-    /**
-     * Tests whether ExtbasePluginEnhancer applies `defaultController` values correctly but keeps additional Query Parameters.
-     *
-     * @param string $additionalParameters
-     * @param string $expectation
-     *
-     * @test
-     *
-     * @dataProvider defaultExtbaseControllerActionNamesAreAppliedWithAdditionalNonMappedQueryArgumentsDataProvider
-     */
-    public function defaultExtbaseControllerActionNamesAreAppliedWithAdditionalNonMappedQueryArguments(string $additionalParameters, string $expectation)
-    {
-        $targetLanguageId = 0;
-        $this->mergeSiteConfiguration('acme-com', [
-            'routeEnhancers' => [
-                'Enhancer' => [
-                    'type' => 'Extbase',
-                    'routes' => [
-                        ['routePath' => '/link/index/{value}', '_controller' => 'Link::index'],
-                        ['routePath' => '/link/list/{value}',  '_controller' => 'Link::list'],
-                        ['routePath' => '/page/show/{value}', '_controller' => 'Page::show'],
-                    ],
-                    'defaultController' => 'Link::index',
-                    'extension' => 'testing',
-                    'plugin' => 'link',
-                    'aspects' => [
-                        'value' => [
-                            'type' => 'StaticValueMapper',
-                            'map' => [
-                                'one' => 1,
-                            ],
-                        ],
-                    ],
-                ]
-            ]
-        ]);
-
-        $response = $this->executeFrontendSubRequest(
-            (new InternalRequest('https://acme.us/'))
-                ->withPageId(1100)
-                ->withInstructions([
-                    $this->createTypoLinkUrlInstruction([
-                        'parameter' => 1100,
-                        'language' => $targetLanguageId,
-                        'additionalParams' => $additionalParameters,
-                        'forceAbsoluteUrl' => 1,
-                    ])
-                ]),
-            $this->internalRequestContext
-        );
-
-        self::assertSame($expectation, (string)$response->getBody());
-    }
-}
diff --git a/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequest/AbstractEnhancerSiteRequestTest.php b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequest/AbstractEnhancerSiteRequestTest.php
new file mode 100644
index 000000000000..9e06dd0a6643
--- /dev/null
+++ b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequest/AbstractEnhancerSiteRequestTest.php
@@ -0,0 +1,224 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\EnhancerSiteRequest;
+
+use TYPO3\CMS\Core\Core\Bootstrap;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\AbstractTestCase;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Builder;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\ExceptionExpectation;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\LanguageContext;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\TestSet;
+use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerFactory;
+use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerWriter;
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequestContext;
+
+/**
+ * Abstract test case
+ */
+abstract class AbstractEnhancerSiteRequestTest extends AbstractTestCase
+{
+    /**
+     * @var InternalRequestContext
+     */
+    protected $internalRequestContext;
+
+    public static function setUpBeforeClass(): void
+    {
+        parent::setUpBeforeClass();
+        static::initializeDatabaseSnapshot();
+    }
+
+    public static function tearDownAfterClass(): void
+    {
+        static::destroyDatabaseSnapshot();
+        parent::tearDownAfterClass();
+    }
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+
+        // these settings are forwarded to the frontend sub-request as well
+        $this->internalRequestContext = (new InternalRequestContext())
+            ->withGlobalSettings(['TYPO3_CONF_VARS' => static::TYPO3_CONF_VARS]);
+
+        $this->writeSiteConfiguration(
+            'acme-com',
+            $this->buildSiteConfiguration(1000, 'https://acme.com/'),
+            [
+                $this->buildDefaultLanguageConfiguration('EN', 'https://acme.us/'),
+                $this->buildLanguageConfiguration('FR', 'https://acme.fr/', ['EN']),
+                $this->buildLanguageConfiguration('FR-CA', 'https://acme.ca/', ['FR', 'EN']),
+            ]
+        );
+
+        $this->writeSiteConfiguration(
+            'archive-acme-com',
+            $this->buildSiteConfiguration(3000, 'https://archive.acme.com/'),
+            [
+                $this->buildDefaultLanguageConfiguration('EN', '/'),
+                $this->buildLanguageConfiguration('FR', 'https://archive.acme.com/fr/', ['EN']),
+                $this->buildLanguageConfiguration('FR-CA', 'https://archive.acme.com/ca/', ['FR', 'EN'])
+            ]
+        );
+
+        $this->withDatabaseSnapshot(function () {
+            $this->setUpDatabase();
+        });
+    }
+
+    protected function tearDown(): void
+    {
+        unset($this->internalRequestContext);
+        parent::tearDown();
+    }
+
+    protected function setUpDatabase()
+    {
+        $backendUser = $this->setUpBackendUserFromFixture(1);
+        Bootstrap::initializeLanguageObject();
+
+        $scenarioFile = __DIR__ . '/../Fixtures/SlugScenario.yaml';
+        $factory = DataHandlerFactory::fromYamlFile($scenarioFile);
+        $writer = DataHandlerWriter::withBackendUser($backendUser);
+        $writer->invokeFactory($factory);
+        static::failIfArrayIsNotEmpty(
+            $writer->getErrors()
+        );
+
+        $this->setUpFrontendRootPage(
+            1000,
+            [
+                'typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/LinkRequest.typoscript',
+            ],
+            [
+                'title' => 'ACME Root',
+            ]
+        );
+
+        $this->setUpFrontendRootPage(
+            3000,
+            [
+                'typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/LinkRequest.typoscript',
+            ],
+            [
+                'title' => 'ACME Archive',
+            ]
+        );
+    }
+
+    /**
+     * This test is re-used in various child classes
+     *
+     * @param TestSet $testSet
+     */
+    protected function pageTypeDecoratorIsApplied(TestSet $testSet): void
+    {
+        $builder = Builder::create();
+        $enhancerConfiguration = $builder->compileEnhancerConfiguration($testSet);
+        $pageTypeConfiguration = $builder->compilePageTypeConfiguration($testSet);
+        $targetUri = $builder->compileUrl($testSet);
+        /** @var LanguageContext $languageContext */
+        $languageContext = $testSet->getSingleApplicable(LanguageContext::class);
+        $expectedLanguageId = $languageContext->getLanguageId();
+        $expectation = $builder->compileResolveArguments($testSet);
+
+        $overrides = [
+            'routeEnhancers' => [
+                'PageType' => $pageTypeConfiguration,
+            ]
+        ];
+        if ($enhancerConfiguration) {
+            $overrides['routeEnhancers']['Enhancer'] = $enhancerConfiguration;
+        }
+        $this->mergeSiteConfiguration('acme-com', $overrides);
+        $this->mergeSiteConfiguration('archive-acme-com', $overrides);
+
+        $allParameters = array_replace_recursive(
+            $expectation['dynamicArguments'],
+            $expectation['staticArguments']
+        );
+        $expectation['pageId'] = $testSet->getTargetPageId();
+        $expectation['languageId'] = $expectedLanguageId;
+        $expectation['requestQueryParams'] = $allParameters;
+        $expectation['_GET'] = $allParameters;
+
+        $response = $this->executeFrontendSubRequest(
+            new InternalRequest($targetUri),
+            $this->internalRequestContext,
+            true
+        );
+
+        $pageArguments = json_decode((string)$response->getBody(), true);
+        self::assertEquals($expectation, $pageArguments);
+    }
+
+    /**
+     * @param TestSet $testSet
+     */
+    protected function assertPageArgumentsEquals(TestSet $testSet)
+    {
+        $builder = Builder::create();
+        $enhancerConfiguration = $builder->compileEnhancerConfiguration($testSet);
+        $targetUri = $builder->compileUrl($testSet);
+        /** @var LanguageContext $languageContext */
+        $languageContext = $testSet->getSingleApplicable(LanguageContext::class);
+        $expectedLanguageId = $languageContext->getLanguageId();
+        $expectation = $builder->compileResolveArguments($testSet);
+
+        $this->mergeSiteConfiguration('acme-com', [
+            'routeEnhancers' => ['Enhancer' => $enhancerConfiguration]
+        ]);
+        $this->mergeSiteConfiguration('archive-acme-com', [
+            'routeEnhancers' => ['Enhancer' => $enhancerConfiguration]
+        ]);
+
+        $allParameters = array_replace_recursive(
+            $expectation['dynamicArguments'],
+            $expectation['staticArguments']
+        );
+        $expectation['pageId'] = $testSet->getTargetPageId();
+        $expectation['pageType'] = '0';
+        $expectation['languageId'] = $expectedLanguageId;
+        $expectation['requestQueryParams'] = $allParameters;
+        $expectation['_GET'] = $allParameters;
+
+        $response = $this->executeFrontendSubRequest(
+            new InternalRequest($targetUri),
+            $this->internalRequestContext,
+            true
+        );
+
+        /** @var ExceptionExpectation $exceptionDeclaration */
+        $exceptionDeclaration = $testSet->getSingleApplicable(ExceptionExpectation::class);
+        if ($exceptionDeclaration !== null) {
+            // @todo This part is "ugly"...
+            self::assertSame(404, $response->getStatusCode());
+            self::assertStringContainsString(
+                // searching in HTML content...
+                htmlspecialchars($exceptionDeclaration->getMessage()),
+                (string)$response->getBody()
+            );
+        } else {
+            $pageArguments = json_decode((string)$response->getBody(), true);
+            self::assertSame(200, $response->getStatusCode());
+            self::assertEquals($expectation, $pageArguments);
+        }
+    }
+}
diff --git a/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequest/LocaleModifierTest.php b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequest/LocaleModifierTest.php
new file mode 100644
index 000000000000..ceac1cea1c07
--- /dev/null
+++ b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequest/LocaleModifierTest.php
@@ -0,0 +1,167 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\EnhancerSiteRequest;
+
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\AspectDeclaration;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Builder;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\LanguageContext;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Permutation;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\TestSet;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariableItem;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Variables;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariablesContext;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariableValue;
+
+/**
+ * Test case
+ */
+class LocaleModifierTest extends AbstractEnhancerSiteRequestTest
+{
+    /**
+     * @param string|TestSet|null $parentSet
+     * @return array
+     */
+    public function localeModifierDataProvider($parentSet = null): array
+    {
+        $builder = Builder::create();
+        // variables (applied when invoking expectations)
+        $variables = Variables::create()->define([
+            'value' => 100,
+            'resolveValue' => 100,
+            'routePrefix' => '{enhance_name}',
+            'aspectName' => 'enhance_name',
+            'inArguments' => 'dynamicArguments' // either 'dynamicArguments' or 'staticArguments'
+        ]);
+        $enhancers = $builder->declareEnhancers();
+        $variableContexts = [
+            VariablesContext::create(
+                Variables::create([
+                    'cHash' => '46227b4ce096dc78a4e71463326c9020',
+                    'cHash2' => 'f80d112e877175ce8e7d54c35bebe12c'
+                ])
+            )->withRequiredApplicables($enhancers['Simple']),
+            VariablesContext::create(
+                Variables::create([
+                    'cHash' => 'e24d3d2d5503baba670d827c3b9470c8',
+                    'cHash2' => '54f45ea94a5e812fbae944792dac940d'
+                ])
+            )->withRequiredApplicables($enhancers['Plugin']),
+            VariablesContext::create(
+                Variables::create([
+                    'cHash' => 'eef21771ab3c3dac3514b4479eedd5ff',
+                    'cHash2' => 'c822555d4ebd106b0d1687e43a4db9c9'
+                ])
+            )->withRequiredApplicables($enhancers['Extbase']),
+        ];
+        return Permutation::create($variables)
+            ->withTargets(
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(0))
+                    ->withTargetPageId(1100)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://acme.us/welcome/enhance/[[value]][[pathSuffix]]?cHash=[[cHash]]',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    ),
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(1))
+                    ->withTargetPageId(1100)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://acme.fr/bienvenue/augmenter/[[value]][[pathSuffix]]?cHash=[[cHash]]',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    ),
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(0))
+                    ->withTargetPageId(3000)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://archive.acme.com/enhance/[[value]][[pathSuffix]]?cHash=[[cHash2]]',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    ),
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(1))
+                    ->withTargetPageId(3000)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://archive.acme.com/fr/augmenter/[[value]][[pathSuffix]]?cHash=[[cHash2]]',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    )
+            )
+            ->withApplicableItems($variableContexts)
+            ->withApplicableItems($enhancers)
+            ->withApplicableSet(
+                AspectDeclaration::create('LocaleModifier')->withConfiguration([
+                    VariableItem::create('aspectName', [
+                        'type' => 'LocaleModifier',
+                        'default' => 'enhance',
+                        'localeMap' => [
+                            [
+                                'locale' => 'fr_FR',
+                                'value' => 'augmenter'
+                            ]
+                        ],
+                    ])
+                ])
+            )
+            ->permute()
+            ->getTargetsForDataProvider();
+    }
+
+    /**
+     * @param TestSet $testSet
+     * @test
+     * @dataProvider localeModifierDataProvider
+     */
+    public function localeModifierIsApplied(TestSet $testSet): void
+    {
+        $this->assertPageArgumentsEquals($testSet);
+    }
+
+    /**
+     * @return array
+     */
+    public function pageTypeDecoratorIsAppliedDataProvider(): array
+    {
+        $testSets = [];
+        foreach (Builder::create()->declarePageTypes() as $pageTypeDeclaration) {
+            $testSet = TestSet::create()
+                ->withMergedApplicables($pageTypeDeclaration)
+                ->withVariables($pageTypeDeclaration->getVariables());
+            $testSets = array_merge(
+                $testSets,
+                $this->localeModifierDataProvider($testSet),
+            );
+        }
+        return $testSets;
+    }
+
+    /**
+     * @param TestSet $testSet
+     * @test
+     * @dataProvider pageTypeDecoratorIsAppliedDataProvider
+     */
+    public function pageTypeDecoratorIsApplied(TestSet $testSet): void
+    {
+        parent::pageTypeDecoratorIsApplied($testSet);
+    }
+}
diff --git a/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequest/PageTypeDecoratorTest.php b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequest/PageTypeDecoratorTest.php
new file mode 100644
index 000000000000..0eba509d432a
--- /dev/null
+++ b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequest/PageTypeDecoratorTest.php
@@ -0,0 +1,141 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\EnhancerSiteRequest;
+
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Builder;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\LanguageContext;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\TestSet;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Variables;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariableValue;
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
+
+/**
+ * Test case
+ */
+class PageTypeDecoratorTest extends AbstractEnhancerSiteRequestTest
+{
+    /**
+     * @return array
+     */
+    public function pageTypeDecoratorIsAppliedDataProvider(): array
+    {
+        $testSets = [];
+        foreach (Builder::create()->declarePageTypes() as $pageTypeDeclaration) {
+            $testSet = TestSet::create()
+                ->withMergedApplicables($pageTypeDeclaration)
+                ->withVariables($pageTypeDeclaration->getVariables());
+
+            $testSetWithoutEnhancers =
+                TestSet::create($testSet)
+                    ->withMergedApplicables(LanguageContext::create(0))
+                    ->withTargetPageId(3000)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://archive.acme.com/[[index]][[pathSuffix]]',
+                            Variables::create(['pathSuffix' => '', 'index' => ''])
+                        )
+                    )
+            ;
+            $testSets = array_merge(
+                $testSets,
+                [$testSetWithoutEnhancers->describe() => [$testSetWithoutEnhancers]],
+            );
+        }
+        return $testSets;
+    }
+
+    /**
+     * @param TestSet $testSet
+     * @test
+     * @dataProvider pageTypeDecoratorIsAppliedDataProvider
+     */
+    public function pageTypeDecoratorIsApplied(TestSet $testSet): void
+    {
+        parent::pageTypeDecoratorIsApplied($testSet);
+    }
+
+    /**
+     * @return array
+     */
+    public function pageTypeDecoratorIndexCanBePartOfSlugDataProvider(): array
+    {
+        $testSets = [];
+        foreach (Builder::create()->declarePageTypes() as $pageTypeDeclaration) {
+            $testSet = TestSet::create()
+                ->withMergedApplicables($pageTypeDeclaration)
+                ->withVariables($pageTypeDeclaration->getVariables());
+
+            $testSetForPageContainingIndexInSlug =
+                TestSet::create($testSet)
+                    ->withMergedApplicables(LanguageContext::create(0))
+                    ->withTargetPageId(3200)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://archive.acme.com/stock-index[[pathSuffix]]',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    )
+            ;
+            $testSets = array_merge(
+                $testSets,
+                [$testSetForPageContainingIndexInSlug->describe() => [$testSetForPageContainingIndexInSlug]]
+            );
+        }
+        return $testSets;
+    }
+
+    /**
+     * @param TestSet $testSet
+     * @test
+     * @dataProvider pageTypeDecoratorIndexCanBePartOfSlugDataProvider
+     */
+    public function pageTypeDecoratorIndexCanBePartOfSlug(TestSet $testSet): void
+    {
+        $builder = Builder::create();
+        $targetUri = $builder->compileUrl($testSet);
+        /** @var LanguageContext $languageContext */
+        $languageContext = $testSet->getSingleApplicable(LanguageContext::class);
+        $expectedLanguageId = $languageContext->getLanguageId();
+        $expectation = $builder->compileResolveArguments($testSet);
+
+        $overrides = [
+            'routeEnhancers' => [
+                'PageType' => $builder->compilePageTypeConfiguration($testSet),
+            ]
+        ];
+        $this->mergeSiteConfiguration('archive-acme-com', $overrides);
+
+        $allParameters = array_replace_recursive(
+            $expectation['dynamicArguments'],
+            $expectation['staticArguments']
+        );
+        $expectation['pageId'] = $testSet->getTargetPageId();
+        $expectation['languageId'] = $expectedLanguageId;
+        $expectation['requestQueryParams'] = $allParameters;
+        $expectation['_GET'] = $allParameters;
+
+        $response = $this->executeFrontendSubRequest(
+            new InternalRequest($targetUri),
+            $this->internalRequestContext,
+            true
+        );
+
+        $pageArguments = json_decode((string)$response->getBody(), true);
+        self::assertEquals($expectation, $pageArguments);
+    }
+}
diff --git a/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequest/PersistedAliasMapperTest.php b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequest/PersistedAliasMapperTest.php
new file mode 100644
index 000000000000..f45d131dd192
--- /dev/null
+++ b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequest/PersistedAliasMapperTest.php
@@ -0,0 +1,122 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\EnhancerSiteRequest;
+
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\AspectDeclaration;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Builder;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\LanguageContext;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Permutation;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\TestSet;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariableItem;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Variables;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariableValue;
+
+/**
+ * Test case
+ */
+class PersistedAliasMapperTest extends AbstractEnhancerSiteRequestTest
+{
+    /**
+     * @param string|TestSet|null $parentSet
+     * @return array
+     */
+    public function persistedAliasMapperDataProvider($parentSet = null): array
+    {
+        $builder = Builder::create();
+        // variables (applied when invoking expectations)
+        $variables = Variables::create()->define([
+            'value' => 1100,
+            'resolveValue' => 1100,
+            'routePrefix' => 'enhance',
+            'aspectName' => 'value',
+            'inArguments' => 'staticArguments' // either 'dynamicArguments' or 'staticArguments'
+        ]);
+        return Permutation::create($variables)
+            ->withTargets(
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(0))
+                    ->withTargetPageId(1100)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://acme.us/welcome/enhance/welcome[[pathSuffix]]',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    ),
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(1))
+                    ->withTargetPageId(1100)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://acme.fr/bienvenue/enhance/bienvenue[[pathSuffix]]',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    )
+            )
+            ->withApplicableItems($builder->declareEnhancers())
+            ->withApplicableSet(
+                AspectDeclaration::create('PersistedAliasMapper')->withConfiguration([
+                    VariableItem::create('aspectName', [
+                        'type' => 'PersistedAliasMapper',
+                        'tableName' => 'pages',
+                        'routeFieldName' => 'slug',
+                        'routeValuePrefix' => '/',
+                    ])
+                ])
+            )
+            ->permute()
+            ->getTargetsForDataProvider();
+    }
+
+    /**
+     * @param TestSet $testSet
+     * @test
+     * @dataProvider persistedAliasMapperDataProvider
+     */
+    public function persistedAliasMapperIsApplied(TestSet $testSet): void
+    {
+        $this->assertPageArgumentsEquals($testSet);
+    }
+
+    /**
+     * @return array
+     */
+    public function pageTypeDecoratorIsAppliedDataProvider(): array
+    {
+        $testSets = [];
+        foreach (Builder::create()->declarePageTypes() as $pageTypeDeclaration) {
+            $testSet = TestSet::create()
+                ->withMergedApplicables($pageTypeDeclaration)
+                ->withVariables($pageTypeDeclaration->getVariables());
+            $testSets = array_merge(
+                $testSets,
+                $this->persistedAliasMapperDataProvider($testSet),
+            );
+        }
+        return $testSets;
+    }
+
+    /**
+     * @param TestSet $testSet
+     * @test
+     * @dataProvider pageTypeDecoratorIsAppliedDataProvider
+     */
+    public function pageTypeDecoratorIsApplied(TestSet $testSet): void
+    {
+        parent::pageTypeDecoratorIsApplied($testSet);
+    }
+}
diff --git a/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequest/PersistedPatternMapperTest.php b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequest/PersistedPatternMapperTest.php
new file mode 100644
index 000000000000..5ba7b50e2bbd
--- /dev/null
+++ b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequest/PersistedPatternMapperTest.php
@@ -0,0 +1,122 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\EnhancerSiteRequest;
+
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\AspectDeclaration;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Builder;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\LanguageContext;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Permutation;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\TestSet;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariableItem;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Variables;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariableValue;
+
+/**
+ * Test case
+ */
+class PersistedPatternMapperTest extends AbstractEnhancerSiteRequestTest
+{
+    /**
+     * @param string|TestSet|null $parentSet
+     * @return array
+     */
+    public function persistedPatternMapperDataProvider($parentSet = null): array
+    {
+        $builder = Builder::create();
+        // variables (applied when invoking expectations)
+        $variables = Variables::create()->define([
+            'value' => 1100,
+            'resolveValue' => 1100,
+            'routePrefix' => 'enhance',
+            'aspectName' => 'value',
+            'inArguments' => 'staticArguments' // either 'dynamicArguments' or 'staticArguments'
+        ]);
+        return Permutation::create($variables)
+            ->withTargets(
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(0))
+                    ->withTargetPageId(1100)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://acme.us/welcome/enhance/hello-and-welcome-[[value]][[pathSuffix]]',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    ),
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(1))
+                    ->withTargetPageId(1100)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://acme.fr/bienvenue/enhance/salut-et-bienvenue-[[value]][[pathSuffix]]',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    )
+            )
+            ->withApplicableItems($builder->declareEnhancers())
+            ->withApplicableSet(
+                AspectDeclaration::create('PersistedPatternMapper')->withConfiguration([
+                    VariableItem::create('aspectName', [
+                        'type' => 'PersistedPatternMapper',
+                        'tableName' => 'pages',
+                        'routeFieldPattern' => '^(?P<subtitle>.+)-(?P<uid>\d+)$',
+                        'routeFieldResult' => '{subtitle}-{uid}',
+                    ])
+                ])
+            )
+            ->permute()
+            ->getTargetsForDataProvider();
+    }
+
+    /**
+     * @param TestSet $testSet
+     * @test
+     * @dataProvider persistedPatternMapperDataProvider
+     */
+    public function persistedPatternMapperIsApplied(TestSet $testSet): void
+    {
+        $this->assertPageArgumentsEquals($testSet);
+    }
+
+    /**
+     * @return array
+     */
+    public function pageTypeDecoratorIsAppliedDataProvider(): array
+    {
+        $testSets = [];
+        foreach (Builder::create()->declarePageTypes() as $pageTypeDeclaration) {
+            $testSet = TestSet::create()
+                ->withMergedApplicables($pageTypeDeclaration)
+                ->withVariables($pageTypeDeclaration->getVariables());
+            $testSets = array_merge(
+                $testSets,
+                $this->persistedPatternMapperDataProvider($testSet),
+            );
+        }
+        return $testSets;
+    }
+
+    /**
+     * @param TestSet $testSet
+     * @test
+     * @dataProvider pageTypeDecoratorIsAppliedDataProvider
+     */
+    public function pageTypeDecoratorIsApplied(TestSet $testSet): void
+    {
+        parent::pageTypeDecoratorIsApplied($testSet);
+    }
+}
diff --git a/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequest/RouteTest.php b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequest/RouteTest.php
new file mode 100644
index 000000000000..be1bdfbbabf7
--- /dev/null
+++ b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequest/RouteTest.php
@@ -0,0 +1,504 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\EnhancerSiteRequest;
+
+use TYPO3\CMS\Core\Error\Http\PageNotFoundException;
+use TYPO3\CMS\Core\Utility\ArrayUtility;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\ApplicableConjunction;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\AspectDeclaration;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Builder;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\EnhancerDeclaration;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\ExceptionExpectation;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\LanguageContext;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Permutation;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\TestSet;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariableItem;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Variables;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariablesContext;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariableValue;
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
+
+/**
+ * Test case
+ */
+class RouteTest extends AbstractEnhancerSiteRequestTest
+{
+    public function routeDefaultsAreConsideredDataProvider($parentSet = null): array
+    {
+        $builder = Builder::create();
+        // variables (applied when invoking expectations)
+        $variables = Variables::create()->define([
+            'uriValue' => '',
+            'resolveValue' => 100,
+            'routePrefix' => 'enhance',
+            'aspectName' => 'value',
+            'inArguments' => 'staticArguments', // either 'dynamicArguments' or 'staticArguments'
+            'otherInArguments' => null,
+        ]);
+        $englishLanguage = LanguageContext::create(0);
+        $frenchLanguage = LanguageContext::create(1);
+        $plainRouteParameter = VariablesContext::create(Variables::create(['routeParameter' => '{value}']));
+        $enforcedRouteParameter = VariablesContext::create(Variables::create(['routeParameter' => '{!value}']));
+        return Permutation::create($variables)
+            ->withTargets(
+                TestSet::create($parentSet)
+                    ->withMergedApplicables($englishLanguage)
+                    ->withTargetPageId(1100)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://acme.us/welcome/enhance[[uriValue]][[pathSuffix]]',
+                            Variables::create(['pathSuffix' => '', 'uriValue' => '/hundred'])
+                        )
+                    ),
+                TestSet::create($parentSet)
+                    ->withMergedApplicables($frenchLanguage)
+                    ->withTargetPageId(1100)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://acme.fr/bienvenue/enhance[[uriValue]][[pathSuffix]]',
+                            Variables::create(['pathSuffix' => '', 'uriValue' => '/cent'])
+                        )
+                    )
+            )
+            ->withApplicableItems($builder->declareEnhancers())
+            ->withApplicableSet(
+                EnhancerDeclaration::create('defaults.value=100')->withConfiguration([
+                    'defaults' => [
+                        'value' => 100,
+                        // it's expected that `other` is NOT applied in page arguments
+                        // since it is not used as `{other}` in `routePath`
+                        'other' => 200,
+                    ]
+                ])
+            )
+            ->withApplicableSet(
+                AspectDeclaration::create('StaticValueMapper')->withConfiguration([
+                    VariableItem::create('aspectName', [
+                        'type' => 'StaticValueMapper',
+                        'map' => [
+                            'hundred' => 100,
+                        ],
+                        'localeMap' => [
+                            [
+                                'locale' => 'fr_FR',
+                                'map' => [
+                                    'cent' => 100,
+                                ],
+                            ]
+                        ],
+                    ])
+                ])
+            )
+            ->withApplicableSet($plainRouteParameter, $enforcedRouteParameter)
+            ->withApplicableSet(
+                // @todo Default route not resolved having enforced route parameter `{!value}`
+                VariablesContext::create(Variables::create([
+                    'uriValue' => null,
+                ]))->withRequiredApplicables($plainRouteParameter),
+                VariablesContext::create(Variables::create([
+                    'uriValue' => '/hundred',
+                ]))->withRequiredApplicables($englishLanguage),
+                VariablesContext::create(Variables::create([
+                    'uriValue' => '/cent',
+                ]))->withRequiredApplicables($frenchLanguage)
+            )
+            ->permute()
+            ->getTargetsForDataProvider();
+    }
+
+    /**
+     * @param TestSet $testSet
+     * @test
+     * @dataProvider routeDefaultsAreConsideredDataProvider
+     */
+    public function routeDefaultsAreConsidered(TestSet $testSet): void
+    {
+        $this->assertPageArgumentsEquals($testSet);
+    }
+
+    public function routeRequirementsHavingAspectsAreConsideredDataProvider($parentSet = null): array
+    {
+        $builder = Builder::create();
+        // variables (applied when invoking expectations)
+        $variables = Variables::create()->define([
+            'routePrefix' => 'enhance',
+            'aspectName' => 'value',
+            'inArguments' => 'staticArguments' // either 'dynamicArguments' or 'staticArguments'
+        ]);
+        return Permutation::create($variables)
+            ->withTargets(
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(0))
+                    ->withTargetPageId(1100)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://acme.us/welcome/enhance/[[value]][[pathSuffix]]',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    )
+            )
+            ->withApplicableSet(
+                VariablesContext::create(Variables::create([
+                    'value' => 'hundred',
+                    'resolveValue' => 100,
+                ])),
+                VariablesContext::create(Variables::create([
+                    'value' => 'hundred/binary',
+                    'resolveValue' => 1100100,
+                ])),
+                ApplicableConjunction::create(
+                    VariablesContext::create(Variables::create([
+                        'value' => 'hundred',
+                        'resolveValue' => 100,
+                    ])),
+                    EnhancerDeclaration::create('requirements.value=/[a-z_/]+/')->withConfiguration([
+                        'requirements' => [
+                            'value' => '[a-z_/]+',
+                        ]
+                    ])
+                ),
+                ApplicableConjunction::create(
+                    VariablesContext::create(Variables::create([
+                        'value' => 'hundred/binary',
+                        'resolveValue' => 1100100,
+                    ])),
+                    EnhancerDeclaration::create('requirements.value=/[a-z_/]+/')->withConfiguration([
+                        'requirements' => [
+                            'value' => '[a-z_/]+',
+                        ]
+                    ])
+                )
+            )
+            ->withApplicableItems($builder->declareEnhancers())
+            ->withApplicableSet(
+                AspectDeclaration::create('StaticValueMapper')->withConfiguration([
+                    VariableItem::create('aspectName', [
+                        'type' => 'StaticValueMapper',
+                        'map' => [
+                            'hundred' => 100,
+                            'hundred/binary' => 1100100,
+                        ],
+                    ])
+                ])
+            )
+            ->permute()
+            ->getTargetsForDataProvider();
+    }
+
+    /**
+     * @param TestSet $testSet
+     * @test
+     * @dataProvider routeRequirementsHavingAspectsAreConsideredDataProvider
+     */
+    public function routeRequirementsHavingAspectsAreConsidered(TestSet $testSet): void
+    {
+        $this->assertPageArgumentsEquals($testSet);
+    }
+
+    public function routeRequirementsAreConsideredDataProvider($parentSet = null): array
+    {
+        $builder = Builder::create();
+        // variables (applied when invoking expectations)
+        $variables = Variables::create()->define([
+            'resolveValue' => 100,
+            'routePrefix' => 'enhance',
+            'aspectName' => 'value',
+            'inArguments' => 'dynamicArguments' // either 'dynamicArguments' or 'staticArguments'
+        ]);
+        $enhancers = $builder->declareEnhancers();
+        $variableContexts = [
+            VariablesContext::create(
+                Variables::create([
+                    'cHash' => '46227b4ce096dc78a4e71463326c9020',
+                ])
+            )->withRequiredApplicables($enhancers['Simple']),
+            VariablesContext::create(
+                Variables::create([
+                    'cHash' => 'e24d3d2d5503baba670d827c3b9470c8',
+                ])
+            )->withRequiredApplicables($enhancers['Plugin']),
+            VariablesContext::create(
+                Variables::create([
+                    'cHash' => 'eef21771ab3c3dac3514b4479eedd5ff',
+                ])
+            )->withRequiredApplicables($enhancers['Extbase']),
+        ];
+        return Permutation::create($variables)
+            ->withTargets(
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(0))
+                    ->withTargetPageId(1100)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://acme.us/welcome/enhance/[[uriValue]][[pathSuffix]]?cHash=[[cHash]]',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    )
+            )
+            ->withApplicableItems($enhancers)
+            ->withApplicableItems($variableContexts)
+            ->withApplicableSet(
+                VariablesContext::create(Variables::create([
+                    'uriValue' => 100,
+                ])),
+                ApplicableConjunction::create(
+                    VariablesContext::create(Variables::create([
+                        'uriValue' => 100,
+                        'cHash' => ''
+                    ])),
+                    ExceptionExpectation::create('Missing cHash')
+                        ->withClassName(PageNotFoundException::class)
+                        ->withMessage('Request parameters could not be validated (&cHash empty)')
+                        ->withCode(1518472189)
+                ),
+                ApplicableConjunction::create(
+                    VariablesContext::create(Variables::create([
+                        'uriValue' => 99,
+                    ])),
+                    ExceptionExpectation::create('too short')
+                        ->withClassName(PageNotFoundException::class)
+                        ->withMessage('The requested page does not exist')
+                        ->withCode(1518472189)
+                ),
+                ApplicableConjunction::create(
+                    VariablesContext::create(Variables::create([
+                        'uriValue' => 99999,
+                    ])),
+                    ExceptionExpectation::create('too long')
+                        ->withClassName(PageNotFoundException::class)
+                        ->withMessage('The requested page does not exist')
+                        ->withCode(1518472189)
+                ),
+                ApplicableConjunction::create(
+                    VariablesContext::create(Variables::create([
+                        'uriValue' => 'NaN',
+                    ])),
+                    ExceptionExpectation::create('NaN')
+                        ->withClassName(PageNotFoundException::class)
+                        ->withMessage('The requested page does not exist')
+                        ->withCode(1518472189)
+                )
+            )
+            ->withApplicableSet(
+                EnhancerDeclaration::create('requirements.value=\\d{3}')->withConfiguration([
+                    'requirements' => [
+                        'value' => '\\d{3}',
+                    ]
+                ])
+            )
+            ->permute()
+            ->getTargetsForDataProvider();
+    }
+
+    /**
+     * @param TestSet $testSet
+     * @test
+     * @dataProvider routeRequirementsAreConsideredDataProvider
+     */
+    public function routeRequirementsAreConsidered(TestSet $testSet): void
+    {
+        $this->assertPageArgumentsEquals($testSet);
+    }
+
+    public function routeIdentifiersAreResolvedDataProvider(): array
+    {
+        return [
+            // namespace[value]
+            'namespace[value] ? test' => [
+                'namespace',
+                'value',
+                'test',
+            ],
+            'namespace[value] ? x^30' => [
+                'namespace',
+                'value',
+                str_repeat('x', 30),
+            ],
+            'namespace[value] ? x^31' => [
+                'namespace',
+                'value',
+                str_repeat('x', 31),
+            ],
+            'namespace[value] ? x^32' => [
+                'namespace',
+                'value',
+                str_repeat('x', 32),
+            ],
+            'namespace[value] ? x^33' => [
+                'namespace',
+                'value',
+                str_repeat('x', 33),
+            ],
+            'namespace[value] ? 1^31 (type-cast)' => [
+                'namespace',
+                'value',
+                str_repeat('1', 31),
+            ],
+            // md5('namespace__@otne3') is 60360798585102000952995164024754 (numeric)
+            // md5('ximaz') is 61529519452809720693702583126814 (numeric)
+            'namespace[@otne3] ? numeric-md5 (type-cast)' => [
+                'namespace',
+                '@otne3',
+                md5('ximaz'),
+            ],
+            'namespace[value] ? namespace__value' => [
+                'namespace',
+                'value',
+                'namespace__value',
+            ],
+            'namespace[value] ? namespace/value' => [
+                'namespace',
+                'value',
+                'namespace/value',
+                'The requested URL is not distinct',
+            ],
+            'namespace[value] ? namespace__other' => [
+                'namespace',
+                'value',
+                'namespace__other',
+            ],
+            'namespace[value] ? namespace/other' => [
+                'namespace',
+                'value',
+                'namespace/other',
+            ],
+            // namespace[any/value]
+            'namespace[any/value] ? x^30' => [
+                'namespace',
+                'any/value',
+                str_repeat('x', 30),
+            ],
+            'namespace[any/value] ? x^31' => [
+                'namespace',
+                'any/value',
+                str_repeat('x', 31),
+            ],
+            'namespace[any/value] ? x^32' => [
+                'namespace',
+                'any/value',
+                str_repeat('x', 32),
+            ],
+            'namespace[any/value] ? namespace__any__value' => [
+                'namespace',
+                'any/value',
+                'namespace__any__value',
+            ],
+            'namespace[any/value] ? namespace/any/value' => [
+                'namespace',
+                'any/value',
+                'namespace/any/value',
+                'The requested URL is not distinct',
+            ],
+            'namespace[any/value] ? namespace__any__other' => [
+                'namespace',
+                'any/value',
+                'namespace__any__other',
+            ],
+            'namespace[any/value] ? namespace/any/other' => [
+                'namespace',
+                'any/value',
+                'namespace/any/other',
+            ],
+            // namespace[@any/value]
+            'namespace[@any/value] ? x^30' => [
+                'namespace',
+                '@any/value',
+                str_repeat('x', 30),
+            ],
+            'namespace[@any/value] ? x^31' => [
+                'namespace',
+                '@any/value',
+                str_repeat('x', 31),
+            ],
+            'namespace[@any/value] ? x^32' => [
+                'namespace',
+                '@any/value',
+                str_repeat('x', 32),
+            ],
+            'namespace[@any/value] ? md5(namespace__@any__value)' => [
+                'namespace',
+                '@any/value',
+                md5('namespace__@any__value'),
+            ],
+            'namespace[@any/value] ? namespace/@any/value' => [
+                'namespace',
+                '@any/value',
+                'namespace/@any/value',
+                'The requested URL is not distinct',
+            ],
+            'namespace[@any/value] ? md5(namespace__@any__other)' => [
+                'namespace',
+                '@any/value',
+                md5('namespace__@any__other'),
+            ],
+            'namespace[@any/value] ? namespace/@any/other' => [
+                'namespace',
+                '@any/value',
+                'namespace/@any/other',
+            ],
+        ];
+    }
+
+    /**
+     * @param string $namespace
+     * @param string $argumentName
+     * @param string $queryPath
+     * @param string|null $failureReason
+     * @test
+     * @dataProvider routeIdentifiersAreResolvedDataProvider
+     */
+    public function routeIdentifiersAreResolved(string $namespace, string $argumentName, string $queryPath, string $failureReason = null)
+    {
+        $query = [];
+        $routeValue = 'route-value';
+        $queryValue = 'parameter-value';
+        $query = ArrayUtility::setValueByPath($query, $queryPath, $queryValue);
+        $queryParameters = http_build_query($query, '', '&', PHP_QUERY_RFC3986);
+        $targetUri = sprintf('https://acme.us/welcome/%s?%s', $routeValue, $queryParameters);
+
+        $this->mergeSiteConfiguration('acme-com', [
+            'routeEnhancers' => ['Enhancer' => [
+                'type' => 'Plugin',
+                'routePath' => '/{name}',
+                '_arguments' => [
+                    'name' => $argumentName,
+                ],
+                'namespace' => $namespace,
+            ]]
+        ]);
+
+        $response = $this->executeFrontendSubRequest(
+            new InternalRequest($targetUri),
+            $this->internalRequestContext,
+            true
+        );
+
+        $body = (string)$response->getBody();
+        if ($failureReason === null) {
+            $pageArguments = json_decode($body, true);
+            self::assertNotNull($pageArguments, 'PageArguments could not be resolved');
+
+            $expected = [];
+            $expected = ArrayUtility::setValueByPath($expected, $namespace . '/' . $argumentName, $routeValue);
+            $expected = ArrayUtility::setValueByPath($expected, $queryPath, $queryValue);
+            self::assertEquals($expected, $pageArguments['requestQueryParams']);
+        } else {
+            self::assertStringContainsString($failureReason, $body);
+        }
+    }
+}
diff --git a/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequest/StaticRangeMapperTest.php b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequest/StaticRangeMapperTest.php
new file mode 100644
index 000000000000..2fd5a9b1d49f
--- /dev/null
+++ b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequest/StaticRangeMapperTest.php
@@ -0,0 +1,133 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\EnhancerSiteRequest;
+
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\AspectDeclaration;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Builder;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\LanguageContext;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Permutation;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\TestSet;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariableItem;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Variables;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariablesContext;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariableValue;
+
+/**
+ * Test case
+ */
+class StaticRangeMapperTest extends AbstractEnhancerSiteRequestTest
+{
+    /**
+     * @param string|TestSet|null $parentSet
+     * @return array
+     */
+    public function staticRangeMapperDataProvider($parentSet = null): array
+    {
+        $variableContexts = array_map(
+            function ($value) {
+                return VariablesContext::create(
+                    Variables::create([
+                        'value' => $value,
+                        'resolveValue' => $value,
+                    ])
+                );
+            },
+            range(10, 100, 30)
+        );
+
+        $builder = Builder::create();
+        // variables (applied when invoking expectations)
+        $variables = Variables::create()->define([
+            'routePrefix' => 'enhance',
+            'aspectName' => 'value',
+            'inArguments' => 'staticArguments' // either 'dynamicArguments' or 'staticArguments'
+        ]);
+        return Permutation::create($variables)
+            ->withTargets(
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(0))
+                    ->withTargetPageId(1100)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://acme.us/welcome/enhance/[[value]][[pathSuffix]]',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    ),
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(1))
+                    ->withTargetPageId(1100)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://acme.fr/bienvenue/enhance/[[value]][[pathSuffix]]',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    )
+            )
+            ->withApplicableItems($variableContexts)
+            ->withApplicableItems($builder->declareEnhancers())
+            ->withApplicableSet(
+                AspectDeclaration::create('StaticRangeMapper')->withConfiguration([
+                    VariableItem::create('aspectName', [
+                        'type' => 'StaticRangeMapper',
+                        'start' => '1',
+                        'end' => '100',
+                    ])
+                ])
+            )
+            ->permute()
+            ->getTargetsForDataProvider();
+    }
+
+    /**
+     * @param TestSet $testSet
+     * @test
+     * @dataProvider staticRangeMapperDataProvider
+     */
+    public function staticRangeMapperIsApplied(TestSet $testSet): void
+    {
+        $this->assertPageArgumentsEquals($testSet);
+    }
+
+    /**
+     * @return array
+     */
+    public function pageTypeDecoratorIsAppliedDataProvider(): array
+    {
+        $testSets = [];
+        foreach (Builder::create()->declarePageTypes() as $pageTypeDeclaration) {
+            $testSet = TestSet::create()
+                ->withMergedApplicables($pageTypeDeclaration)
+                ->withVariables($pageTypeDeclaration->getVariables());
+            $testSets = array_merge(
+                $testSets,
+                $this->staticRangeMapperDataProvider($testSet)
+            );
+        }
+        return $testSets;
+    }
+
+    /**
+     * @param TestSet $testSet
+     * @test
+     * @dataProvider pageTypeDecoratorIsAppliedDataProvider
+     */
+    public function pageTypeDecoratorIsApplied(TestSet $testSet): void
+    {
+        parent::pageTypeDecoratorIsApplied($testSet);
+    }
+}
diff --git a/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequest/StaticValueMapperTest.php b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequest/StaticValueMapperTest.php
new file mode 100644
index 000000000000..1ea9c6bc6dbe
--- /dev/null
+++ b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequest/StaticValueMapperTest.php
@@ -0,0 +1,130 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\EnhancerSiteRequest;
+
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\AspectDeclaration;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Builder;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\LanguageContext;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Permutation;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\TestSet;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariableItem;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Variables;
+use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariableValue;
+
+/**
+ * Test case
+ */
+class StaticValueMapperTest extends AbstractEnhancerSiteRequestTest
+{
+    /**
+     * @param string|TestSet|null $parentSet
+     * @return array
+     */
+    public function staticValueMapperDataProvider($parentSet = null): array
+    {
+        $builder = Builder::create();
+        // variables (applied when invoking expectations)
+        $variables = Variables::create()->define([
+            'value' => 100,
+            'resolveValue' => 100,
+            'routePrefix' => 'enhance',
+            'aspectName' => 'value',
+            'inArguments' => 'staticArguments' // either 'dynamicArguments' or 'staticArguments'
+        ]);
+        return Permutation::create($variables)
+            ->withTargets(
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(0))
+                    ->withTargetPageId(1100)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://acme.us/welcome/enhance/hundred[[pathSuffix]]',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    ),
+                TestSet::create($parentSet)
+                    ->withMergedApplicables(LanguageContext::create(1))
+                    ->withTargetPageId(1100)
+                    ->withUrl(
+                        VariableValue::create(
+                            'https://acme.fr/bienvenue/enhance/cent[[pathSuffix]]',
+                            Variables::create(['pathSuffix' => ''])
+                        )
+                    )
+            )
+            ->withApplicableItems($builder->declareEnhancers())
+            ->withApplicableSet(
+                AspectDeclaration::create('StaticValueMapper')->withConfiguration([
+                    VariableItem::create('aspectName', [
+                        'type' => 'StaticValueMapper',
+                        'map' => [
+                            'hundred' => 100,
+                        ],
+                        'localeMap' => [
+                            [
+                                'locale' => 'fr_FR',
+                                'map' => [
+                                    'cent' => 100,
+                                ],
+                            ]
+                        ],
+                    ])
+                ])
+            )
+            ->permute()
+            ->getTargetsForDataProvider();
+    }
+
+    /**
+     * @param TestSet $testSet
+     * @test
+     * @dataProvider staticValueMapperDataProvider
+     */
+    public function staticValueMapperIsApplied(TestSet $testSet): void
+    {
+        $this->assertPageArgumentsEquals($testSet);
+    }
+
+    /**
+     * @return array
+     */
+    public function pageTypeDecoratorIsAppliedDataProvider(): array
+    {
+        $testSets = [];
+        foreach (Builder::create()->declarePageTypes() as $pageTypeDeclaration) {
+            $testSet = TestSet::create()
+                ->withMergedApplicables($pageTypeDeclaration)
+                ->withVariables($pageTypeDeclaration->getVariables());
+            $testSets = array_merge(
+                $testSets,
+                $this->staticValueMapperDataProvider($testSet),
+            );
+        }
+        return $testSets;
+    }
+
+    /**
+     * @param TestSet $testSet
+     * @test
+     * @dataProvider pageTypeDecoratorIsAppliedDataProvider
+     */
+    public function pageTypeDecoratorIsApplied(TestSet $testSet): void
+    {
+        parent::pageTypeDecoratorIsApplied($testSet);
+    }
+}
diff --git a/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequestTest.php b/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequestTest.php
deleted file mode 100644
index 8b7f6d0ff77b..000000000000
--- a/typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequestTest.php
+++ /dev/null
@@ -1,1181 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-
-namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling;
-
-use TYPO3\CMS\Core\Core\Bootstrap;
-use TYPO3\CMS\Core\Error\Http\PageNotFoundException;
-use TYPO3\CMS\Core\Utility\ArrayUtility;
-use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\ApplicableConjunction;
-use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\AspectDeclaration;
-use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Builder;
-use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\EnhancerDeclaration;
-use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\ExceptionExpectation;
-use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\LanguageContext;
-use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Permutation;
-use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\TestSet;
-use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariableItem;
-use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\Variables;
-use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariablesContext;
-use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Framework\Builder\VariableValue;
-use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerFactory;
-use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerWriter;
-use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
-use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequestContext;
-
-/**
- * Test case for frontend requests having site handling configured using enhancers.
- */
-class EnhancerSiteRequestTest extends AbstractTestCase
-{
-    /**
-     * @var InternalRequestContext
-     */
-    private $internalRequestContext;
-
-    public static function setUpBeforeClass(): void
-    {
-        parent::setUpBeforeClass();
-        static::initializeDatabaseSnapshot();
-    }
-
-    public static function tearDownAfterClass(): void
-    {
-        static::destroyDatabaseSnapshot();
-        parent::tearDownAfterClass();
-    }
-
-    protected function setUp(): void
-    {
-        parent::setUp();
-
-        // these settings are forwarded to the frontend sub-request as well
-        $this->internalRequestContext = (new InternalRequestContext())
-            ->withGlobalSettings(['TYPO3_CONF_VARS' => static::TYPO3_CONF_VARS]);
-
-        $this->writeSiteConfiguration(
-            'acme-com',
-            $this->buildSiteConfiguration(1000, 'https://acme.com/'),
-            [
-                $this->buildDefaultLanguageConfiguration('EN', 'https://acme.us/'),
-                $this->buildLanguageConfiguration('FR', 'https://acme.fr/', ['EN']),
-                $this->buildLanguageConfiguration('FR-CA', 'https://acme.ca/', ['FR', 'EN']),
-            ]
-        );
-
-        $this->writeSiteConfiguration(
-            'archive-acme-com',
-            $this->buildSiteConfiguration(3000, 'https://archive.acme.com/'),
-            [
-                $this->buildDefaultLanguageConfiguration('EN', '/'),
-                $this->buildLanguageConfiguration('FR', 'https://archive.acme.com/fr/', ['EN']),
-                $this->buildLanguageConfiguration('FR-CA', 'https://archive.acme.com/ca/', ['FR', 'EN'])
-            ]
-        );
-
-        $this->withDatabaseSnapshot(function () {
-            $this->setUpDatabase();
-        });
-    }
-
-    protected function setUpDatabase()
-    {
-        $backendUser = $this->setUpBackendUserFromFixture(1);
-        Bootstrap::initializeLanguageObject();
-
-        $scenarioFile = __DIR__ . '/Fixtures/SlugScenario.yaml';
-        $factory = DataHandlerFactory::fromYamlFile($scenarioFile);
-        $writer = DataHandlerWriter::withBackendUser($backendUser);
-        $writer->invokeFactory($factory);
-        static::failIfArrayIsNotEmpty(
-            $writer->getErrors()
-        );
-
-        $this->setUpFrontendRootPage(
-            1000,
-            [
-                'typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/LinkRequest.typoscript',
-            ],
-            [
-                'title' => 'ACME Root',
-            ]
-        );
-
-        $this->setUpFrontendRootPage(
-            3000,
-            [
-                'typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/LinkRequest.typoscript',
-            ],
-            [
-                'title' => 'ACME Archive',
-            ]
-        );
-    }
-
-    protected function tearDown(): void
-    {
-        unset($this->internalRequestContext);
-        parent::tearDown();
-    }
-
-    /**
-     * @param string|TestSet|null $parentSet
-     * @return array
-     */
-    public function localeModifierDataProvider($parentSet = null): array
-    {
-        $builder = Builder::create();
-        // variables (applied when invoking expectations)
-        $variables = Variables::create()->define([
-            'value' => 100,
-            'resolveValue' => 100,
-            'routePrefix' => '{enhance_name}',
-            'aspectName' => 'enhance_name',
-            'inArguments' => 'dynamicArguments' // either 'dynamicArguments' or 'staticArguments'
-        ]);
-        $enhancers = $builder->declareEnhancers();
-        $variableContexts = [
-            VariablesContext::create(
-                Variables::create([
-                    'cHash' => '46227b4ce096dc78a4e71463326c9020',
-                    'cHash2' => 'f80d112e877175ce8e7d54c35bebe12c'
-                ])
-            )->withRequiredApplicables($enhancers['Simple']),
-            VariablesContext::create(
-                Variables::create([
-                    'cHash' => 'e24d3d2d5503baba670d827c3b9470c8',
-                    'cHash2' => '54f45ea94a5e812fbae944792dac940d'
-                ])
-            )->withRequiredApplicables($enhancers['Plugin']),
-            VariablesContext::create(
-                Variables::create([
-                    'cHash' => 'eef21771ab3c3dac3514b4479eedd5ff',
-                    'cHash2' => 'c822555d4ebd106b0d1687e43a4db9c9'
-                ])
-            )->withRequiredApplicables($enhancers['Extbase']),
-        ];
-        return Permutation::create($variables)
-            ->withTargets(
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(0))
-                    ->withTargetPageId(1100)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://acme.us/welcome/enhance/[[value]][[pathSuffix]]?cHash=[[cHash]]',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    ),
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(1))
-                    ->withTargetPageId(1100)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://acme.fr/bienvenue/augmenter/[[value]][[pathSuffix]]?cHash=[[cHash]]',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    ),
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(0))
-                    ->withTargetPageId(3000)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://archive.acme.com/enhance/[[value]][[pathSuffix]]?cHash=[[cHash2]]',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    ),
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(1))
-                    ->withTargetPageId(3000)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://archive.acme.com/fr/augmenter/[[value]][[pathSuffix]]?cHash=[[cHash2]]',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    )
-            )
-            ->withApplicableItems($variableContexts)
-            ->withApplicableItems($enhancers)
-            ->withApplicableSet(
-                AspectDeclaration::create('LocaleModifier')->withConfiguration([
-                    VariableItem::create('aspectName', [
-                        'type' => 'LocaleModifier',
-                        'default' => 'enhance',
-                        'localeMap' => [
-                            [
-                                'locale' => 'fr_FR',
-                                'value' => 'augmenter'
-                            ]
-                        ],
-                    ])
-                ])
-            )
-            ->permute()
-            ->getTargetsForDataProvider();
-    }
-
-    /**
-     * @param TestSet $testSet
-     *
-     * @test
-     * @dataProvider localeModifierDataProvider
-     */
-    public function localeModifierIsApplied(TestSet $testSet): void
-    {
-        $this->assertPageArgumentsEquals($testSet);
-    }
-
-    /**
-     * @param string|TestSet|null $parentSet
-     * @return array
-     */
-    public function persistedAliasMapperDataProvider($parentSet = null): array
-    {
-        $builder = Builder::create();
-        // variables (applied when invoking expectations)
-        $variables = Variables::create()->define([
-            'value' => 1100,
-            'resolveValue' => 1100,
-            'routePrefix' => 'enhance',
-            'aspectName' => 'value',
-            'inArguments' => 'staticArguments' // either 'dynamicArguments' or 'staticArguments'
-        ]);
-        return Permutation::create($variables)
-            ->withTargets(
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(0))
-                    ->withTargetPageId(1100)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://acme.us/welcome/enhance/welcome[[pathSuffix]]',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    ),
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(1))
-                    ->withTargetPageId(1100)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://acme.fr/bienvenue/enhance/bienvenue[[pathSuffix]]',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    )
-            )
-            ->withApplicableItems($builder->declareEnhancers())
-            ->withApplicableSet(
-                AspectDeclaration::create('PersistedAliasMapper')->withConfiguration([
-                    VariableItem::create('aspectName', [
-                        'type' => 'PersistedAliasMapper',
-                        'tableName' => 'pages',
-                        'routeFieldName' => 'slug',
-                        'routeValuePrefix' => '/',
-                    ])
-                ])
-            )
-            ->permute()
-            ->getTargetsForDataProvider();
-    }
-
-    /**
-     * @param TestSet $testSet
-     *
-     * @test
-     * @dataProvider persistedAliasMapperDataProvider
-     */
-    public function persistedAliasMapperIsApplied(TestSet $testSet): void
-    {
-        $this->assertPageArgumentsEquals($testSet);
-    }
-
-    /**
-     * @param string|TestSet|null $parentSet
-     * @return array
-     */
-    public function persistedPatternMapperDataProvider($parentSet = null): array
-    {
-        $builder = Builder::create();
-        // variables (applied when invoking expectations)
-        $variables = Variables::create()->define([
-            'value' => 1100,
-            'resolveValue' => 1100,
-            'routePrefix' => 'enhance',
-            'aspectName' => 'value',
-            'inArguments' => 'staticArguments' // either 'dynamicArguments' or 'staticArguments'
-        ]);
-        return Permutation::create($variables)
-            ->withTargets(
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(0))
-                    ->withTargetPageId(1100)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://acme.us/welcome/enhance/hello-and-welcome-[[value]][[pathSuffix]]',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    ),
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(1))
-                    ->withTargetPageId(1100)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://acme.fr/bienvenue/enhance/salut-et-bienvenue-[[value]][[pathSuffix]]',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    )
-            )
-            ->withApplicableItems($builder->declareEnhancers())
-            ->withApplicableSet(
-                AspectDeclaration::create('PersistedPatternMapper')->withConfiguration([
-                    VariableItem::create('aspectName', [
-                        'type' => 'PersistedPatternMapper',
-                        'tableName' => 'pages',
-                        'routeFieldPattern' => '^(?P<subtitle>.+)-(?P<uid>\d+)$',
-                        'routeFieldResult' => '{subtitle}-{uid}',
-                    ])
-                ])
-            )
-            ->permute()
-            ->getTargetsForDataProvider();
-    }
-
-    /**
-     * @param TestSet $testSet
-     *
-     * @test
-     * @dataProvider persistedPatternMapperDataProvider
-     */
-    public function persistedPatternMapperIsApplied(TestSet $testSet): void
-    {
-        $this->assertPageArgumentsEquals($testSet);
-    }
-
-    /**
-     * @param string|TestSet|null $parentSet
-     * @return array
-     */
-    public function staticValueMapperDataProvider($parentSet = null): array
-    {
-        $builder = Builder::create();
-        // variables (applied when invoking expectations)
-        $variables = Variables::create()->define([
-            'value' => 100,
-            'resolveValue' => 100,
-            'routePrefix' => 'enhance',
-            'aspectName' => 'value',
-            'inArguments' => 'staticArguments' // either 'dynamicArguments' or 'staticArguments'
-        ]);
-        return Permutation::create($variables)
-            ->withTargets(
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(0))
-                    ->withTargetPageId(1100)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://acme.us/welcome/enhance/hundred[[pathSuffix]]',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    ),
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(1))
-                    ->withTargetPageId(1100)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://acme.fr/bienvenue/enhance/cent[[pathSuffix]]',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    )
-            )
-            ->withApplicableItems($builder->declareEnhancers())
-            ->withApplicableSet(
-                AspectDeclaration::create('StaticValueMapper')->withConfiguration([
-                    VariableItem::create('aspectName', [
-                        'type' => 'StaticValueMapper',
-                        'map' => [
-                            'hundred' => 100,
-                        ],
-                        'localeMap' => [
-                            [
-                                'locale' => 'fr_FR',
-                                'map' => [
-                                    'cent' => 100,
-                                ],
-                            ]
-                        ],
-                    ])
-                ])
-            )
-            ->permute()
-            ->getTargetsForDataProvider();
-    }
-
-    /**
-     * @param TestSet $testSet
-     *
-     * @test
-     * @dataProvider staticValueMapperDataProvider
-     */
-    public function staticValueMapperIsApplied(TestSet $testSet): void
-    {
-        $this->assertPageArgumentsEquals($testSet);
-    }
-
-    /**
-     * @param string|TestSet|null $parentSet
-     * @return array
-     */
-    public function staticRangeMapperDataProvider($parentSet = null): array
-    {
-        $variableContexts = array_map(
-            function ($value) {
-                return VariablesContext::create(
-                    Variables::create([
-                        'value' => $value,
-                        'resolveValue' => $value,
-                    ])
-                );
-            },
-            range(10, 100, 30)
-        );
-
-        $builder = Builder::create();
-        // variables (applied when invoking expectations)
-        $variables = Variables::create()->define([
-            'routePrefix' => 'enhance',
-            'aspectName' => 'value',
-            'inArguments' => 'staticArguments' // either 'dynamicArguments' or 'staticArguments'
-        ]);
-        return Permutation::create($variables)
-            ->withTargets(
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(0))
-                    ->withTargetPageId(1100)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://acme.us/welcome/enhance/[[value]][[pathSuffix]]',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    ),
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(1))
-                    ->withTargetPageId(1100)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://acme.fr/bienvenue/enhance/[[value]][[pathSuffix]]',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    )
-            )
-            ->withApplicableItems($variableContexts)
-            ->withApplicableItems($builder->declareEnhancers())
-            ->withApplicableSet(
-                AspectDeclaration::create('StaticRangeMapper')->withConfiguration([
-                    VariableItem::create('aspectName', [
-                        'type' => 'StaticRangeMapper',
-                        'start' => '1',
-                        'end' => '100',
-                    ])
-                ])
-            )
-            ->permute()
-            ->getTargetsForDataProvider();
-    }
-
-    /**
-     * @param TestSet $testSet
-     *
-     * @test
-     * @dataProvider staticRangeMapperDataProvider
-     */
-    public function staticRangeMapperIsApplied(TestSet $testSet): void
-    {
-        $this->assertPageArgumentsEquals($testSet);
-    }
-
-    /**
-     * @return array
-     */
-    public function pageTypeDecoratorIsAppliedDataProvider(): array
-    {
-        $testSets = [];
-        foreach (Builder::create()->declarePageTypes() as $pageTypeDeclaration) {
-            $testSet = TestSet::create()
-                ->withMergedApplicables($pageTypeDeclaration)
-                ->withVariables($pageTypeDeclaration->getVariables());
-
-            $testSetWithoutEnhancers =
-                TestSet::create($testSet)
-                    ->withMergedApplicables(LanguageContext::create(0))
-                    ->withTargetPageId(3000)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://archive.acme.com/[[index]][[pathSuffix]]',
-                            Variables::create(['pathSuffix' => '', 'index' => ''])
-                        )
-                    )
-            ;
-            $testSets = array_merge(
-                $testSets,
-                [$testSetWithoutEnhancers->describe() => [$testSetWithoutEnhancers]],
-                $this->localeModifierDataProvider($testSet),
-                $this->persistedAliasMapperDataProvider($testSet),
-                $this->persistedPatternMapperDataProvider($testSet),
-                $this->staticValueMapperDataProvider($testSet),
-                $this->staticRangeMapperDataProvider($testSet)
-            );
-        }
-        return $testSets;
-    }
-
-    /**
-     * @param TestSet $testSet
-     *
-     * @test
-     * @dataProvider pageTypeDecoratorIsAppliedDataProvider
-     */
-    public function pageTypeDecoratorIsApplied(TestSet $testSet): void
-    {
-        $builder = Builder::create();
-        $enhancerConfiguration = $builder->compileEnhancerConfiguration($testSet);
-        $pageTypeConfiguration = $builder->compilePageTypeConfiguration($testSet);
-        $targetUri = $builder->compileUrl($testSet);
-        /** @var LanguageContext $languageContext */
-        $languageContext = $testSet->getSingleApplicable(LanguageContext::class);
-        $expectedLanguageId = $languageContext->getLanguageId();
-        $expectation = $builder->compileResolveArguments($testSet);
-
-        $overrides = [
-            'routeEnhancers' => [
-                'PageType' => $pageTypeConfiguration,
-            ]
-        ];
-        if ($enhancerConfiguration) {
-            $overrides['routeEnhancers']['Enhancer'] = $enhancerConfiguration;
-        }
-        $this->mergeSiteConfiguration('acme-com', $overrides);
-        $this->mergeSiteConfiguration('archive-acme-com', $overrides);
-
-        $allParameters = array_replace_recursive(
-            $expectation['dynamicArguments'],
-            $expectation['staticArguments']
-        );
-        $expectation['pageId'] = $testSet->getTargetPageId();
-        $expectation['languageId'] = $expectedLanguageId;
-        $expectation['requestQueryParams'] = $allParameters;
-        $expectation['_GET'] = $allParameters;
-
-        $response = $this->executeFrontendSubRequest(
-            new InternalRequest($targetUri),
-            $this->internalRequestContext,
-            true
-        );
-
-        $pageArguments = json_decode((string)$response->getBody(), true);
-        self::assertEquals($expectation, $pageArguments);
-    }
-
-    /**
-     * @return array
-     */
-    public function pageTypeDecoratorIndexCanBePartOfSlugDataProvider(): array
-    {
-        $testSets = [];
-        foreach (Builder::create()->declarePageTypes() as $pageTypeDeclaration) {
-            $testSet = TestSet::create()
-                ->withMergedApplicables($pageTypeDeclaration)
-                ->withVariables($pageTypeDeclaration->getVariables());
-
-            $testSetForPageContainingIndexInSlug =
-                TestSet::create($testSet)
-                    ->withMergedApplicables(LanguageContext::create(0))
-                    ->withTargetPageId(3200)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://archive.acme.com/stock-index[[pathSuffix]]',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    )
-            ;
-            $testSets = array_merge(
-                $testSets,
-                [$testSetForPageContainingIndexInSlug->describe() => [$testSetForPageContainingIndexInSlug]]
-            );
-        }
-        return $testSets;
-    }
-
-    /**
-     * @param TestSet $testSet
-     *
-     * @test
-     * @dataProvider pageTypeDecoratorIndexCanBePartOfSlugDataProvider
-     */
-    public function pageTypeDecoratorIndexCanBePartOfSlug(TestSet $testSet): void
-    {
-        $builder = Builder::create();
-        $targetUri = $builder->compileUrl($testSet);
-        /** @var LanguageContext $languageContext */
-        $languageContext = $testSet->getSingleApplicable(LanguageContext::class);
-        $expectedLanguageId = $languageContext->getLanguageId();
-        $expectation = $builder->compileResolveArguments($testSet);
-
-        $overrides = [
-            'routeEnhancers' => [
-                'PageType' => $builder->compilePageTypeConfiguration($testSet),
-            ]
-        ];
-        $this->mergeSiteConfiguration('archive-acme-com', $overrides);
-
-        $allParameters = array_replace_recursive(
-            $expectation['dynamicArguments'],
-            $expectation['staticArguments']
-        );
-        $expectation['pageId'] = $testSet->getTargetPageId();
-        $expectation['languageId'] = $expectedLanguageId;
-        $expectation['requestQueryParams'] = $allParameters;
-        $expectation['_GET'] = $allParameters;
-
-        $response = $this->executeFrontendSubRequest(
-            new InternalRequest($targetUri),
-            $this->internalRequestContext,
-            true
-        );
-
-        $pageArguments = json_decode((string)$response->getBody(), true);
-        self::assertEquals($expectation, $pageArguments);
-    }
-
-    public function routeDefaultsAreConsideredDataProvider($parentSet = null): array
-    {
-        $builder = Builder::create();
-        // variables (applied when invoking expectations)
-        $variables = Variables::create()->define([
-            'uriValue' => '',
-            'resolveValue' => 100,
-            'routePrefix' => 'enhance',
-            'aspectName' => 'value',
-            'inArguments' => 'staticArguments', // either 'dynamicArguments' or 'staticArguments'
-            'otherInArguments' => null,
-        ]);
-        $enhancerDeclarations = $builder->declareEnhancers();
-        $englishLanguage = LanguageContext::create(0);
-        $frenchLanguage = LanguageContext::create(1);
-        $plainRouteParameter = VariablesContext::create(Variables::create(['routeParameter' => '{value}']));
-        $enforcedRouteParameter = VariablesContext::create(Variables::create(['routeParameter' => '{!value}']));
-        return Permutation::create($variables)
-            ->withTargets(
-                TestSet::create($parentSet)
-                    ->withMergedApplicables($englishLanguage)
-                    ->withTargetPageId(1100)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://acme.us/welcome/enhance[[uriValue]][[pathSuffix]]',
-                            Variables::create(['pathSuffix' => '', 'uriValue' => '/hundred'])
-                        )
-                    ),
-                TestSet::create($parentSet)
-                    ->withMergedApplicables($frenchLanguage)
-                    ->withTargetPageId(1100)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://acme.fr/bienvenue/enhance[[uriValue]][[pathSuffix]]',
-                            Variables::create(['pathSuffix' => '', 'uriValue' => '/cent'])
-                        )
-                    )
-            )
-            ->withApplicableItems($builder->declareEnhancers())
-            ->withApplicableSet(
-                EnhancerDeclaration::create('defaults.value=100')->withConfiguration([
-                    'defaults' => [
-                        'value' => 100,
-                        // it's expected that `other` is NOT applied in page arguments
-                        // since it is not used as `{other}` in `routePath`
-                        'other' => 200,
-                    ]
-                ])
-            )
-            ->withApplicableSet(
-                AspectDeclaration::create('StaticValueMapper')->withConfiguration([
-                    VariableItem::create('aspectName', [
-                        'type' => 'StaticValueMapper',
-                        'map' => [
-                            'hundred' => 100,
-                        ],
-                        'localeMap' => [
-                            [
-                                'locale' => 'fr_FR',
-                                'map' => [
-                                    'cent' => 100,
-                                ],
-                            ]
-                        ],
-                    ])
-                ])
-            )
-            ->withApplicableSet($plainRouteParameter, $enforcedRouteParameter)
-            ->withApplicableSet(
-                // @todo Default route not resolved having enforced route parameter `{!value}`
-                VariablesContext::create(Variables::create([
-                    'uriValue' => null,
-                ]))->withRequiredApplicables($plainRouteParameter),
-                VariablesContext::create(Variables::create([
-                    'uriValue' => '/hundred',
-                ]))->withRequiredApplicables($englishLanguage),
-                VariablesContext::create(Variables::create([
-                    'uriValue' => '/cent',
-                ]))->withRequiredApplicables($frenchLanguage)
-            )
-            ->permute()
-            ->getTargetsForDataProvider();
-    }
-
-    /**
-     * @param TestSet $testSet
-     *
-     * @test
-     * @dataProvider routeDefaultsAreConsideredDataProvider
-     */
-    public function routeDefaultsAreConsidered(TestSet $testSet): void
-    {
-        $this->assertPageArgumentsEquals($testSet);
-    }
-
-    public function routeRequirementsHavingAspectsAreConsideredDataProvider($parentSet = null): array
-    {
-        $builder = Builder::create();
-        // variables (applied when invoking expectations)
-        $variables = Variables::create()->define([
-            'routePrefix' => 'enhance',
-            'aspectName' => 'value',
-            'inArguments' => 'staticArguments' // either 'dynamicArguments' or 'staticArguments'
-        ]);
-        return Permutation::create($variables)
-            ->withTargets(
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(0))
-                    ->withTargetPageId(1100)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://acme.us/welcome/enhance/[[value]][[pathSuffix]]',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    )
-            )
-            ->withApplicableSet(
-                VariablesContext::create(Variables::create([
-                    'value' => 'hundred',
-                    'resolveValue' => 100,
-                ])),
-                VariablesContext::create(Variables::create([
-                    'value' => 'hundred/binary',
-                    'resolveValue' => 1100100,
-                ])),
-                ApplicableConjunction::create(
-                    VariablesContext::create(Variables::create([
-                        'value' => 'hundred',
-                        'resolveValue' => 100,
-                    ])),
-                    EnhancerDeclaration::create('requirements.value=/[a-z_/]+/')->withConfiguration([
-                        'requirements' => [
-                            'value' => '[a-z_/]+',
-                        ]
-                    ])
-                ),
-                ApplicableConjunction::create(
-                    VariablesContext::create(Variables::create([
-                        'value' => 'hundred/binary',
-                        'resolveValue' => 1100100,
-                    ])),
-                    EnhancerDeclaration::create('requirements.value=/[a-z_/]+/')->withConfiguration([
-                        'requirements' => [
-                            'value' => '[a-z_/]+',
-                        ]
-                    ])
-                )
-            )
-            ->withApplicableItems($builder->declareEnhancers())
-            ->withApplicableSet(
-                AspectDeclaration::create('StaticValueMapper')->withConfiguration([
-                    VariableItem::create('aspectName', [
-                        'type' => 'StaticValueMapper',
-                        'map' => [
-                            'hundred' => 100,
-                            'hundred/binary' => 1100100,
-                        ],
-                    ])
-                ])
-            )
-            ->permute()
-            ->getTargetsForDataProvider();
-    }
-
-    /**
-     * @param TestSet $testSet
-     *
-     * @test
-     * @dataProvider routeRequirementsHavingAspectsAreConsideredDataProvider
-     */
-    public function routeRequirementsHavingAspectsAreConsidered(TestSet $testSet): void
-    {
-        $this->assertPageArgumentsEquals($testSet);
-    }
-
-    public function routeRequirementsAreConsideredDataProvider($parentSet = null): array
-    {
-        $builder = Builder::create();
-        // variables (applied when invoking expectations)
-        $variables = Variables::create()->define([
-            'resolveValue' => 100,
-            'routePrefix' => 'enhance',
-            'aspectName' => 'value',
-            'inArguments' => 'dynamicArguments' // either 'dynamicArguments' or 'staticArguments'
-        ]);
-        $enhancers = $builder->declareEnhancers();
-        $variableContexts = [
-            VariablesContext::create(
-                Variables::create([
-                    'cHash' => '46227b4ce096dc78a4e71463326c9020',
-                ])
-            )->withRequiredApplicables($enhancers['Simple']),
-            VariablesContext::create(
-                Variables::create([
-                    'cHash' => 'e24d3d2d5503baba670d827c3b9470c8',
-                ])
-            )->withRequiredApplicables($enhancers['Plugin']),
-            VariablesContext::create(
-                Variables::create([
-                    'cHash' => 'eef21771ab3c3dac3514b4479eedd5ff',
-                ])
-            )->withRequiredApplicables($enhancers['Extbase']),
-        ];
-        return Permutation::create($variables)
-            ->withTargets(
-                TestSet::create($parentSet)
-                    ->withMergedApplicables(LanguageContext::create(0))
-                    ->withTargetPageId(1100)
-                    ->withUrl(
-                        VariableValue::create(
-                            'https://acme.us/welcome/enhance/[[uriValue]][[pathSuffix]]?cHash=[[cHash]]',
-                            Variables::create(['pathSuffix' => ''])
-                        )
-                    )
-            )
-            ->withApplicableItems($enhancers)
-            ->withApplicableItems($variableContexts)
-            ->withApplicableSet(
-                VariablesContext::create(Variables::create([
-                    'uriValue' => 100,
-                ])),
-                ApplicableConjunction::create(
-                    VariablesContext::create(Variables::create([
-                        'uriValue' => 100,
-                        'cHash' => ''
-                    ])),
-                    ExceptionExpectation::create('Missing cHash')
-                        ->withClassName(PageNotFoundException::class)
-                        ->withMessage('Request parameters could not be validated (&cHash empty)')
-                        ->withCode(1518472189)
-                ),
-                ApplicableConjunction::create(
-                    VariablesContext::create(Variables::create([
-                        'uriValue' => 99,
-                    ])),
-                    ExceptionExpectation::create('too short')
-                        ->withClassName(PageNotFoundException::class)
-                        ->withMessage('The requested page does not exist')
-                        ->withCode(1518472189)
-                ),
-                ApplicableConjunction::create(
-                    VariablesContext::create(Variables::create([
-                        'uriValue' => 99999,
-                    ])),
-                    ExceptionExpectation::create('too long')
-                        ->withClassName(PageNotFoundException::class)
-                        ->withMessage('The requested page does not exist')
-                        ->withCode(1518472189)
-                ),
-                ApplicableConjunction::create(
-                    VariablesContext::create(Variables::create([
-                        'uriValue' => 'NaN',
-                    ])),
-                    ExceptionExpectation::create('NaN')
-                        ->withClassName(PageNotFoundException::class)
-                        ->withMessage('The requested page does not exist')
-                        ->withCode(1518472189)
-                )
-            )
-            ->withApplicableSet(
-                EnhancerDeclaration::create('requirements.value=\\d{3}')->withConfiguration([
-                    'requirements' => [
-                        'value' => '\\d{3}',
-                    ]
-                ])
-            )
-            ->permute()
-            ->getTargetsForDataProvider();
-    }
-
-    /**
-     * @param TestSet $testSet
-     *
-     * @test
-     * @dataProvider routeRequirementsAreConsideredDataProvider
-     */
-    public function routeRequirementsAreConsidered(TestSet $testSet): void
-    {
-        $this->assertPageArgumentsEquals($testSet);
-    }
-
-    public function routeIdentifiersAreResolvedDataProvider(): array
-    {
-        return [
-            // namespace[value]
-            'namespace[value] ? test' => [
-                'namespace',
-                'value',
-                'test',
-            ],
-            'namespace[value] ? x^30' => [
-                'namespace',
-                'value',
-                str_repeat('x', 30),
-            ],
-            'namespace[value] ? x^31' => [
-                'namespace',
-                'value',
-                str_repeat('x', 31),
-            ],
-            'namespace[value] ? x^32' => [
-                'namespace',
-                'value',
-                str_repeat('x', 32),
-            ],
-            'namespace[value] ? x^33' => [
-                'namespace',
-                'value',
-                str_repeat('x', 33),
-            ],
-            'namespace[value] ? 1^31 (type-cast)' => [
-                'namespace',
-                'value',
-                str_repeat('1', 31),
-            ],
-            // md5('namespace__@otne3') is 60360798585102000952995164024754 (numeric)
-            // md5('ximaz') is 61529519452809720693702583126814 (numeric)
-            'namespace[@otne3] ? numeric-md5 (type-cast)' => [
-                'namespace',
-                '@otne3',
-                md5('ximaz'),
-            ],
-            'namespace[value] ? namespace__value' => [
-                'namespace',
-                'value',
-                'namespace__value',
-            ],
-            'namespace[value] ? namespace/value' => [
-                'namespace',
-                'value',
-                'namespace/value',
-                'The requested URL is not distinct',
-            ],
-            'namespace[value] ? namespace__other' => [
-                'namespace',
-                'value',
-                'namespace__other',
-            ],
-            'namespace[value] ? namespace/other' => [
-                'namespace',
-                'value',
-                'namespace/other',
-            ],
-            // namespace[any/value]
-            'namespace[any/value] ? x^30' => [
-                'namespace',
-                'any/value',
-                str_repeat('x', 30),
-            ],
-            'namespace[any/value] ? x^31' => [
-                'namespace',
-                'any/value',
-                str_repeat('x', 31),
-            ],
-            'namespace[any/value] ? x^32' => [
-                'namespace',
-                'any/value',
-                str_repeat('x', 32),
-            ],
-            'namespace[any/value] ? namespace__any__value' => [
-                'namespace',
-                'any/value',
-                'namespace__any__value',
-            ],
-            'namespace[any/value] ? namespace/any/value' => [
-                'namespace',
-                'any/value',
-                'namespace/any/value',
-                'The requested URL is not distinct',
-            ],
-            'namespace[any/value] ? namespace__any__other' => [
-                'namespace',
-                'any/value',
-                'namespace__any__other',
-            ],
-            'namespace[any/value] ? namespace/any/other' => [
-                'namespace',
-                'any/value',
-                'namespace/any/other',
-            ],
-            // namespace[@any/value]
-            'namespace[@any/value] ? x^30' => [
-                'namespace',
-                '@any/value',
-                str_repeat('x', 30),
-            ],
-            'namespace[@any/value] ? x^31' => [
-                'namespace',
-                '@any/value',
-                str_repeat('x', 31),
-            ],
-            'namespace[@any/value] ? x^32' => [
-                'namespace',
-                '@any/value',
-                str_repeat('x', 32),
-            ],
-            'namespace[@any/value] ? md5(namespace__@any__value)' => [
-                'namespace',
-                '@any/value',
-                md5('namespace__@any__value'),
-            ],
-            'namespace[@any/value] ? namespace/@any/value' => [
-                'namespace',
-                '@any/value',
-                'namespace/@any/value',
-                'The requested URL is not distinct',
-            ],
-            'namespace[@any/value] ? md5(namespace__@any__other)' => [
-                'namespace',
-                '@any/value',
-                md5('namespace__@any__other'),
-            ],
-            'namespace[@any/value] ? namespace/@any/other' => [
-                'namespace',
-                '@any/value',
-                'namespace/@any/other',
-            ],
-        ];
-    }
-
-    /**
-     * @param string $namespace
-     * @param string $argumentName
-     * @param string $queryPath
-     * @param string|null $failureReason
-     *
-     * @test
-     * @dataProvider routeIdentifiersAreResolvedDataProvider
-     */
-    public function routeIdentifiersAreResolved(string $namespace, string $argumentName, string $queryPath, string $failureReason = null)
-    {
-        $query = [];
-        $routeValue = 'route-value';
-        $queryValue = 'parameter-value';
-        $query = ArrayUtility::setValueByPath($query, $queryPath, $queryValue);
-        $queryParameters = http_build_query($query, '', '&', PHP_QUERY_RFC3986);
-        $targetUri = sprintf('https://acme.us/welcome/%s?%s', $routeValue, $queryParameters);
-
-        $this->mergeSiteConfiguration('acme-com', [
-            'routeEnhancers' => ['Enhancer' => [
-                'type' => 'Plugin',
-                'routePath' => '/{name}',
-                '_arguments' => [
-                    'name' => $argumentName,
-                ],
-                'namespace' => $namespace,
-            ]]
-        ]);
-
-        $response = $this->executeFrontendSubRequest(
-            new InternalRequest($targetUri),
-            $this->internalRequestContext,
-            true
-        );
-
-        $body = (string)$response->getBody();
-        if ($failureReason === null) {
-            $pageArguments = json_decode($body, true);
-            self::assertNotNull($pageArguments, 'PageArguments could not be resolved');
-
-            $expected = [];
-            $expected = ArrayUtility::setValueByPath($expected, $namespace . '/' . $argumentName, $routeValue);
-            $expected = ArrayUtility::setValueByPath($expected, $queryPath, $queryValue);
-            self::assertEquals($expected, $pageArguments['requestQueryParams']);
-        } else {
-            self::assertStringContainsString($failureReason, $body);
-        }
-    }
-
-    /**
-     * @param TestSet $testSet
-     */
-    protected function assertPageArgumentsEquals(TestSet $testSet)
-    {
-        $builder = Builder::create();
-        $enhancerConfiguration = $builder->compileEnhancerConfiguration($testSet);
-        $targetUri = $builder->compileUrl($testSet);
-        /** @var LanguageContext $languageContext */
-        $languageContext = $testSet->getSingleApplicable(LanguageContext::class);
-        $expectedLanguageId = $languageContext->getLanguageId();
-        $expectation = $builder->compileResolveArguments($testSet);
-
-        $this->mergeSiteConfiguration('acme-com', [
-            'routeEnhancers' => ['Enhancer' => $enhancerConfiguration]
-        ]);
-        $this->mergeSiteConfiguration('archive-acme-com', [
-            'routeEnhancers' => ['Enhancer' => $enhancerConfiguration]
-        ]);
-
-        $allParameters = array_replace_recursive(
-            $expectation['dynamicArguments'],
-            $expectation['staticArguments']
-        );
-        $expectation['pageId'] = $testSet->getTargetPageId();
-        $expectation['pageType'] = '0';
-        $expectation['languageId'] = $expectedLanguageId;
-        $expectation['requestQueryParams'] = $allParameters;
-        $expectation['_GET'] = $allParameters;
-
-        $response = $this->executeFrontendSubRequest(
-            new InternalRequest($targetUri),
-            $this->internalRequestContext,
-            true
-        );
-
-        /** @var ExceptionExpectation $exceptionDeclaration */
-        $exceptionDeclaration = $testSet->getSingleApplicable(ExceptionExpectation::class);
-        if ($exceptionDeclaration !== null) {
-            // @todo This part is "ugly"...
-            self::assertSame(404, $response->getStatusCode());
-            self::assertStringContainsString(
-                // searching in HTML content...
-                htmlspecialchars($exceptionDeclaration->getMessage()),
-                (string)$response->getBody()
-            );
-        } else {
-            $pageArguments = json_decode((string)$response->getBody(), true);
-            self::assertSame(200, $response->getStatusCode());
-            self::assertEquals($expectation, $pageArguments);
-        }
-    }
-}
-- 
GitLab