Skip to content

Commit

Permalink
ENH Update AttributesHTML to output alt attribute even if it's empty
Browse files Browse the repository at this point in the history
  • Loading branch information
Maxime Rainville committed May 13, 2024
1 parent 4429a49 commit 3a703eb
Show file tree
Hide file tree
Showing 3 changed files with 329 additions and 3 deletions.
6 changes: 3 additions & 3 deletions src/View/AttributesHTML.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,9 @@ public function getAttributesHTML($attributes = null)

$attributes = (array) $attributes;

$attributes = array_filter($attributes ?? [], function ($v) {
return ($v || $v === 0 || $v === '0');
});
$attributes = array_filter($attributes ?? [], function ($v, $k) {
return ($k === 'alt' || $v || $v === 0 || $v === '0');
}, ARRAY_FILTER_USE_BOTH);

if ($exclude) {
$attributes = array_diff_key(
Expand Down
293 changes: 293 additions & 0 deletions tests/php/View/AttributesHTMLTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
<?php

namespace SilverStripe\View\Tests;

use SilverStripe\ORM\ArrayLib;
use SilverStripe\ORM\FieldType\DBVarchar;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\View\ArrayData;

class AttributesHTMLTest extends SapphireTest
{

public function singleAttributeProvider(): array
{
return [
'empty string' => ['test', '', 'Empty string is not converted to a different falsy value'],
'Zero' => ['test', 0, 'Zero is not converted to a different falsy value'],
'Null' => ['test', 0, 'Null is not converted to a different falsy value'],
'False' => ['test', false, 'False is not converted to a different falsy value'],
'Empty array' => ['test', [], 'Empty array is not converted to a different falsy value'],
'True' => ['test', true, 'True is stored properly as an attribute'],
'String' => ['test', 'test', 'String is stored properly as an attribute'],
'Int' => ['test', -1, 'Int is stored properly as an attribute'],
'Array' => ['test', ['foo' => 'bar'], 'Array is stored properly as an attribute'],
];
}

/** @dataProvider singleAttributeProvider */
public function testGetAttribute($name, $value, $message): void
{
$dummy = new AttributesHTMLTest\DummyAttributesHTML();
$this->assertNull(
$dummy->getAttribute('non-existent attribute'),
'Trying to access a non-existent attribute should return null'
);

$dummy->setAttribute($name, $value);
$this->assertSame(
$value,
$dummy->getAttribute($name),
$message
);

}

public function testGetAttributes(): void
{
$dummy = new AttributesHTMLTest\DummyAttributesHTML();
$dummy->setDefaultAttributes([]);
$this->assertSame(
[],
$dummy->getAttributes(),
'When no attributes are set and the default attributes are empty, an empty array should be returned'
);

$dummy->setAttribute('empty', '');
$dummy->setAttribute('foo', 'bar');
$dummy->setAttribute('Number', 123);
$dummy->setAttribute('Array', ['foo' => 'bar']);

$this->assertSame(
[
'empty' => '',
'foo' => 'bar',
'Number' => 123,
'Array' => ['foo' => 'bar'],
],
$dummy->getAttributes(),
'All explicitly defined attributes should be returned'
);

$dummy = new AttributesHTMLTest\DummyAttributesHTML();
$dummy->setDefaultAttributes([
'foo' => 'Will be overridden',
'bar' => 'Not overridden',
]);
$this->assertSame(
[
'foo' => 'Will be overridden',
'bar' => 'Not overridden',
],
$dummy->getAttributes(),
'When no attributes are set and the default attributes are used'
);

$dummy->setAttribute('empty', '');
$dummy->setAttribute('foo', 'bar');
$dummy->setAttribute('Number', 123);
$dummy->setAttribute('Array', ['foo' => 'bar']);

$this->assertSame(
[
'foo' => 'bar',
'bar' => 'Not overridden',
'empty' => '',
'Number' => 123,
'Array' => ['foo' => 'bar'],
],
$dummy->getAttributes(),
'Explicitly defined attributes overrides default ones'
);
}

public function testAttributesHTML(): void
{
$dummy = new AttributesHTMLTest\DummyAttributesHTML();

$dummy->setAttribute('emptystring', '');
$dummy->setAttribute('nullvalue', null);
$dummy->setAttribute('false', false);
$dummy->setAttribute('emptyarray', []);
$dummy->setAttribute('zeroint', 0);
$dummy->setAttribute('zerostring', '0');
$dummy->setAttribute('alt', '');
$dummy->setAttribute('array', ['foo' => 'bar']);
$dummy->setAttribute('width', 123);
$dummy->setAttribute('hack>', '">hack');

$html = $dummy->getAttributesHTML();

$this->assertStringNotContainsString(
'emptystring',
$html,
'Attribute with empty string are not rendered'
);

$this->assertStringNotContainsString(
'nullvalue',
$html,
'Attribute with null are not rendered'
);

$this->assertStringNotContainsString(
'false',
$html,
'Attribute with false are not rendered'
);

$this->assertStringNotContainsString(
'emptyarray',
$html,
'Attribute with empty array are not rendered'
);

$this->assertStringContainsString(
'zeroint="0"',
$html,
'Attribute with a zero int value are rendered'
);

$this->assertStringContainsString(
'zerostring="0"',
$html,
'Attribute with a zerostring value are rendered'
);

$this->assertStringContainsString(
'alt=""',
$html,
'alt attribute is rendered even when empty set to an empty string'
);

$this->assertStringContainsString(
'array="{&quot;foo&quot;:&quot;bar&quot;}"',
$html,
'Array attribute is converted to JSON'
);

$this->assertStringContainsString(
'width="123"',
$html,
'Numeric values are rendered with quotes'
);

$this->assertStringNotContainsString(
'hack&quot;&gt;="&quot;&gt;hack"',
$html,
'Attribute names and value are escaped'
);

$html = $dummy->getAttributesHTML('zeroint', 'array');

$this->assertStringNotContainsString(
'zeroint="0"',
$html,
'Excluded attributes are not rendered'
);

$this->assertStringContainsString(
'zerostring="0"',
$html,
'Attribute not excluded still render'
);

$this->assertStringContainsString(
'alt=""',
$html,
'Attribute not excluded still render'
);

$this->assertStringNotContainsString(
'array',
$html,
'Excluded attributes are not rendered'
);
}

public function testAttributesHTMLwithExplicitAttr(): void
{
$dummy = new AttributesHTMLTest\DummyAttributesHTML();

$this->assertEmpty(
'',
$dummy->getAttributesHTML(),
'If no attributes are provided, an empty string should be returned'
);

$attributes = [
'emptystring' => '',
'nullvalue' => null,
'false' => false,
'emptyarray' => [],
'zeroint' => 0,
'zerostring' => '0',
'alt' => '',
'array' => ['foo' => 'bar'],
'width' => 123,
'hack>' => '">hack',
];

$html = $dummy->getAttributesHTML($attributes);

$this->assertStringNotContainsString(
'emptystring',
$html,
'Attribute with empty string are not rendered'
);

$this->assertStringNotContainsString(
'nullvalue',
$html,
'Attribute with null are not rendered'
);

$this->assertStringNotContainsString(
'false',
$html,
'Attribute with false are not rendered'
);

$this->assertStringNotContainsString(
'emptyarray',
$html,
'Attribute with empty array are not rendered'
);

$this->assertStringContainsString(
'zeroint="0"',
$html,
'Attribute with a zero int value are rendered'
);

$this->assertStringContainsString(
'zerostring="0"',
$html,
'Attribute with a zerostring value are rendered'
);

$this->assertStringContainsString(
'alt=""',
$html,
'alt attribute is rendered even when empty set to an empty string'
);

$this->assertStringContainsString(
'array="{&quot;foo&quot;:&quot;bar&quot;}"',
$html,
'Array attribute is converted to JSON'
);

$this->assertStringContainsString(
'width="123"',
$html,
'Numeric values are rendered with quotes'
);

$this->assertStringNotContainsString(
'hack&quot;&gt;="&quot;&gt;hack"',
$html,
'Attribute names and value are escaped'
);
}
}
33 changes: 33 additions & 0 deletions tests/php/View/AttributesHTMLTest/DummyAttributesHTML.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace SilverStripe\View\Tests\AttributesHTMLTest;

use SilverStripe\View\AttributesHTML;
use SilverStripe\Dev\TestOnly;

/**
* This call is used to test the AttributesHTML trait
*/
class DummyAttributesHTML implements TestOnly
{
use AttributesHTML;

private array $defaultAttributes = [];

/**
* Trait requires this method to prepopulate the attributes
*/
protected function getDefaultAttributes(): array
{
return $this->defaultAttributes;
}

/**
* This method is only there to allow to explicitly set the default attributes in the test.
*/
public function setDefaultAttributes(array $attributes): void
{
$this->defaultAttributes = $attributes;
}

}

0 comments on commit 3a703eb

Please sign in to comment.