diff --git a/src/View/AttributesHTML.php b/src/View/AttributesHTML.php index 66f066d30..88295046a 100644 --- a/src/View/AttributesHTML.php +++ b/src/View/AttributesHTML.php @@ -105,9 +105,9 @@ trait AttributesHTML $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( diff --git a/tests/php/View/AttributesHTMLTest.php b/tests/php/View/AttributesHTMLTest.php new file mode 100644 index 000000000..36f48b89a --- /dev/null +++ b/tests/php/View/AttributesHTMLTest.php @@ -0,0 +1,290 @@ + ['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 provideGetAttribute */ + public function testGetAttribute($name, $value, $message): void + { + $dummy = new 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 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 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 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="{"foo":"bar"}"', + $html, + 'Array attribute is converted to JSON' + ); + + $this->assertStringContainsString( + 'width="123"', + $html, + 'Numeric values are rendered with quotes' + ); + + $this->assertStringNotContainsString( + 'hack">="">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 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="{"foo":"bar"}"', + $html, + 'Array attribute is converted to JSON' + ); + + $this->assertStringContainsString( + 'width="123"', + $html, + 'Numeric values are rendered with quotes' + ); + + $this->assertStringNotContainsString( + 'hack">="">hack"', + $html, + 'Attribute names and value are escaped' + ); + } +} diff --git a/tests/php/View/AttributesHTMLTest/DummyAttributesHTML.php b/tests/php/View/AttributesHTMLTest/DummyAttributesHTML.php new file mode 100644 index 000000000..655baf033 --- /dev/null +++ b/tests/php/View/AttributesHTMLTest/DummyAttributesHTML.php @@ -0,0 +1,32 @@ +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; + } +}