Skip to content

Commit

Permalink
[BUGFIX] Re-enable arbitrary aria- and data- arguments (#872)
Browse files Browse the repository at this point in the history
* [TASK] Add test coverage for data- and aria-

* [BUGFIX] Re-enable arbitrary aria- and data- arguments

A previous optimation broke the ability to use arbitrary
ViewHelper attributes in TagBasedViewHelpers that start with
aria-* and data-*. This patch re-implements this functionality.

A following patch will allow all arbitrary attributes for
TagBasedViewHelpers. This is a pre-patch to fix the existing
regression in Fluid.
  • Loading branch information
s2b authored Jun 10, 2024
1 parent bbf1ad5 commit b00bc31
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 147 deletions.
10 changes: 10 additions & 0 deletions src/Core/ViewHelper/AbstractTagBasedViewHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,16 @@ public function handleAdditionalArguments(array $arguments)
parent::handleAdditionalArguments($arguments);
}

public function validateAdditionalArguments(array $arguments)
{
foreach (array_keys($arguments) as $name) {
if (str_starts_with($name, 'data-') || str_starts_with($name, 'aria-')) {
unset($arguments[$name]);
}
}
parent::validateAdditionalArguments($arguments);
}

/**
* @return string
*/
Expand Down
38 changes: 19 additions & 19 deletions src/Core/ViewHelper/AbstractViewHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -558,26 +558,26 @@ public function convert(TemplateCompiler $templateCompiler): array
$argumentName,
is_array($defaultValue) && empty($defaultValue) ? '[]' : var_export($defaultValue, true),
);
} else {
// Argument *is* given to VH, resolve
$argumentValue = $arguments[$argumentName];
if ($argumentValue instanceof NodeInterface) {
$converted = $argumentValue->convert($templateCompiler);
if (!empty($converted['initialization'])) {
$accumulatedArgumentInitializationCode .= $converted['initialization'];
}
$argumentInitializationCode .= sprintf(
'\'%s\' => %s,' . chr(10),
$argumentName,
$converted['execution'],
);
} else {
$argumentInitializationCode .= sprintf(
'\'%s\' => %s,' . chr(10),
$argumentName,
$argumentValue,
);
}
}

foreach ($arguments as $argumentName => $argumentValue) {
if ($argumentValue instanceof NodeInterface) {
$converted = $argumentValue->convert($templateCompiler);
if (!empty($converted['initialization'])) {
$accumulatedArgumentInitializationCode .= $converted['initialization'];
}
$argumentInitializationCode .= sprintf(
'\'%s\' => %s,' . chr(10),
$argumentName,
$converted['execution'],
);
} else {
$argumentInitializationCode .= sprintf(
'\'%s\' => %s,' . chr(10),
$argumentName,
$argumentValue,
);
}
}

Expand Down
211 changes: 89 additions & 122 deletions tests/Functional/Cases/TagBasedTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,154 +9,121 @@

namespace TYPO3Fluid\Fluid\Tests\Functional\Cases;

use TYPO3Fluid\Fluid\Core\Rendering\RenderingContext;
use TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperInvoker;
use TYPO3Fluid\Fluid\Tests\BaseTestCase;
use TYPO3Fluid\Fluid\Tests\Functional\Fixtures\ViewHelpers\TagBasedTestViewHelper;
use TYPO3Fluid\Fluid\Tests\Functional\AbstractFunctionalTestCase;
use TYPO3Fluid\Fluid\View\TemplateView;

final class TagBasedTest extends BaseTestCase
final class TagBasedTest extends AbstractFunctionalTestCase
{
/**
* @test
*/
public function tagBasedViewHelperWithAdditionalAttributesArray(): void
public static function renderTagBasedViewHelperDataProvider(): array
{
$invoker = new ViewHelperInvoker();
$viewHelper = new TagBasedTestViewHelper();
$arguments = [
'additionalAttributes' => [
'foo' => 'bar',
return [
'registered argument' => [
'<test:tagBasedTest registeredArgument="test" />',
'<div />',
],
];
$result = $invoker->invoke($viewHelper, $arguments, new RenderingContext());
self::assertSame('<div foo="bar" />', $result);
}

/**
* @test
*/
public function tagBasedViewHelperWithDataArray(): void
{
$invoker = new ViewHelperInvoker();
$viewHelper = new TagBasedTestViewHelper();
$arguments = [
'data' => [
'foo' => 'bar',
'registered tag attribute' => [
'<test:tagBasedTest registeredTagAttribute="test" />',
'<div registeredTagAttribute="test" />',
],
];
$result = $invoker->invoke($viewHelper, $arguments, new RenderingContext());
self::assertSame('<div data-foo="bar" />', $result);
}

/**
* @test
*/
public function tagBasedViewHelperWithAriaArray(): void
{
$invoker = new ViewHelperInvoker();
$viewHelper = new TagBasedTestViewHelper();
$arguments = [
'aria' => [
'controls' => 'foo',
'data array' => [
'<test:tagBasedTest data="{foo: \'bar\', more: 1}" />',
'<div data-foo="bar" data-more="1" />',
],
'aria array' => [
'<test:tagBasedTest aria="{foo: \'bar\', more: 1}" />',
'<div aria-foo="bar" aria-more="1" />',
],
'data attribute' => [
'<test:tagBasedTest data-foo="bar" />',
'<div data-foo="bar" />',
],
'aria attribute' => [
'<test:tagBasedTest aria-foo="bar" />',
'<div aria-foo="bar" />',
],
'data array before data attribute' => [
'<test:tagBasedTest data="{foo: \'data\'}" data-foo="attribute" />',
'<div data-foo="attribute" />',
],
'data array after data attribute' => [
'<test:tagBasedTest data-foo="attribute" data="{foo: \'data\'}" />',
'<div data-foo="attribute" />',
],
'aria array before aria attribute' => [
'<test:tagBasedTest aria="{foo: \'aria\'}" aria-foo="attribute" />',
'<div aria-foo="attribute" />',
],
'aria array after aria attribute' => [
'<test:tagBasedTest aria-foo="attribute" aria="{foo: \'aria\'}" />',
'<div aria-foo="attribute" />',
],
'additional attributes' => [
'<test:tagBasedTest additionalAttributes="{data-foo: \'bar\'}" />',
'<div data-foo="bar" />',
],
'additional attributes and data array' => [
'<test:tagBasedTest additionalAttributes="{data-foo: \'additional\'}" data="{foo: \'data\'}" />',
'<div data-foo="data" />',
],
'additional attributes and data attribute' => [
'<test:tagBasedTest additionalAttributes="{data-foo: \'additional\'}" data-foo="attribute" />',
'<div data-foo="attribute" />',
],
];
$result = $invoker->invoke($viewHelper, $arguments, new RenderingContext());
self::assertSame('<div aria-controls="foo" />', $result);
}

/**
* @test
* @dataProvider renderTagBasedViewHelperDataProvider
*/
public function tagBasedViewHelperWithDataPrefixedArgument(): void
public function renderTagBasedViewHelper(string $source, string $expected): void
{
$invoker = new ViewHelperInvoker();
$viewHelper = new TagBasedTestViewHelper();
$arguments = [
'data-foo' => 'bar',
];
$result = $invoker->invoke($viewHelper, $arguments, new RenderingContext());
self::assertSame('<div data-foo="bar" />', $result);
}
$view = new TemplateView();
$view->getRenderingContext()->setCache(self::$cache);
$view->getRenderingContext()->getTemplatePaths()->setTemplateSource($source);
$view->getViewHelperResolver()->addNamespace('test', 'TYPO3Fluid\\Fluid\\Tests\\Functional\\Fixtures\\ViewHelpers');
$output = $view->render();
self::assertEquals($expected, $output);

/**
* @test
*/
public function tagBasedViewHelperWithAriaPrefixedArgument(): void
{
$invoker = new ViewHelperInvoker();
$viewHelper = new TagBasedTestViewHelper();
$arguments = [
'aria-controls' => 'foo',
];
$result = $invoker->invoke($viewHelper, $arguments, new RenderingContext());
self::assertSame('<div aria-controls="foo" />', $result);
// Second run to test cached template parsing
$view = new TemplateView();
$view->getRenderingContext()->setCache(self::$cache);
$view->getRenderingContext()->getTemplatePaths()->setTemplateSource($source);
$view->getViewHelperResolver()->addNamespace('test', 'TYPO3Fluid\\Fluid\\Tests\\Functional\\Fixtures\\ViewHelpers');
$output = $view->render();
self::assertEquals($expected, $output);
}

public static function tagBasedViewHelperWithDataArrayAndPrefixedArgumentProvider(): array
public static function throwsErrorForInvalidArgumentTypesDatProvider(): array
{
return [
'data before attribute' => [
[
'data' => [
'foo' => 'data',
],
'data-foo' => 'attribute',
],
],
'attribute before data' => [
[
'data-foo' => 'attribute',
'data' => [
'foo' => 'data',
],
],
'data argument as string' => [
'<test:tagBasedTest data="test" />',
\InvalidArgumentException::class,
],
];
}

/**
* @dataProvider tagBasedViewHelperWithDataArrayAndPrefixedArgumentProvider
* @test
*/
public function tagBasedViewHelperWithDataArrayAndPrefixedArgument(array $arguments): void
{
$invoker = new ViewHelperInvoker();
$viewHelper = new TagBasedTestViewHelper();
$result = $invoker->invoke($viewHelper, $arguments, new RenderingContext());
self::assertSame('<div data-foo="attribute" />', $result);
}

public static function tagBasedViewHelperWithAriaArrayAndPrefixedArgumentProvider(): array
{
return [
'aria before attribute' => [
[
'aria' => [
'controls' => 'aria',
],
'aria-controls' => 'attribute',
],
],
'attribute before aria' => [
[
'aria-controls' => 'attribute',
'aria' => [
'controls' => 'aria',
],
],
'aria argument as string' => [
'<test:tagBasedTest aria="test" />',
\InvalidArgumentException::class,
],
'undefined argument' => [
'<test:tagBasedTest undefinedArgument="test" />',
\TYPO3Fluid\Fluid\Core\ViewHelper\Exception::class,
],
];
}

/**
* @dataProvider tagBasedViewHelperWithAriaArrayAndPrefixedArgumentProvider
* @test
* @dataProvider throwsErrorForInvalidArgumentTypesDatProvider
*/
public function tagBasedViewHelperWithAriaArrayAndPrefixedArgument(array $arguments): void
public function throwsErrorForInvalidArgumentTypes(string $source, string $exceptionClass): void
{
$invoker = new ViewHelperInvoker();
$viewHelper = new TagBasedTestViewHelper();
$result = $invoker->invoke($viewHelper, $arguments, new RenderingContext());
self::assertSame('<div aria-controls="attribute" />', $result);
self::expectException($exceptionClass);

$view = new TemplateView();
$view->getRenderingContext()->setCache(self::$cache);
$view->getRenderingContext()->getTemplatePaths()->setTemplateSource($source);
$view->getViewHelperResolver()->addNamespace('test', 'TYPO3Fluid\\Fluid\\Tests\\Functional\\Fixtures\\ViewHelpers');
$view->render();
}
}
10 changes: 4 additions & 6 deletions tests/Functional/Fixtures/ViewHelpers/TagBasedTestViewHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,11 @@

class TagBasedTestViewHelper extends AbstractTagBasedViewHelper
{
public function prepareArguments(): array
public function initializeArguments(): void
{
// Override to avoid the static cache of registered ViewHelper arguments; will always return
// only those arguments that are registered in this particular instance.
$this->argumentDefinitions = [];
parent::initializeArguments();
$this->registerArgument('registeredArgument', 'string', 'test argument');
$this->registerTagAttribute('registeredTagAttribute', 'string', 'test argument');
$this->registerUniversalTagAttributes();
$this->initializeArguments();
return $this->argumentDefinitions;
}
}

0 comments on commit b00bc31

Please sign in to comment.