Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 3.9.1 under development

- Bug #232: Render `loading` attribute before `src` (@samdark)
- Chg #234: Remove tag attributes sorting

## 3.9.0 November 29, 2024

Expand Down
49 changes: 0 additions & 49 deletions src/Html.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,45 +96,6 @@
*/
final class Html
{
/**
* The preferred order of attributes in a tag. This mainly affects the order of the attributes that are
* rendered by {@see renderTagAttributes()}.
*/
private const ATTRIBUTE_ORDER = [
'type',
'id',
'class',
'name',
'value',

'href',
'loading',
'src',
'srcset',
'form',
'action',
'method',

'selected',
'checked',
'readonly',
'disabled',
'multiple',

'size',
'maxlength',
'minlength',
'width',
'height',
'rows',
'cols',

'alt',
'title',
'rel',
'media',
];

/**
* List of tag attributes that should be specially handled when their values are of array type.
* In particular, if the value of the `data` attribute is `['name' => 'xyz', 'age' => 13]`, two attributes will be
Expand Down Expand Up @@ -1613,16 +1574,6 @@
*/
public static function renderTagAttributes(array $attributes): string
{
if (count($attributes) > 1) {
$sorted = [];
foreach (self::ATTRIBUTE_ORDER as $name) {
if (isset($attributes[$name])) {
$sorted[$name] = $attributes[$name];
}
}
$attributes = array_merge($sorted, $attributes);
}

$html = '';
/**
* @var string $name
Expand All @@ -1637,11 +1588,11 @@
/** @psalm-var array<array-key, scalar[]|string|Stringable|null> $value */
foreach ($value as $n => $v) {
if (!isset($v)) {
continue;

Check warning on line 1591 in src/Html.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.3-ubuntu-latest

Escaped Mutant for Mutator "Continue_": --- Original +++ New @@ @@ /** @psalm-var array<array-key, scalar[]|string|Stringable|null> $value */ foreach ($value as $n => $v) { if (!isset($v)) { - continue; + break; } $fullName = "{$name}-{$n}"; if (in_array($fullName, self::ATTRIBUTES_WITH_CONCATENATED_VALUES, true)) {
}
$fullName = "$name-$n";
if (in_array($fullName, self::ATTRIBUTES_WITH_CONCATENATED_VALUES, true)) {
$html .= self::renderAttribute(

Check warning on line 1595 in src/Html.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.3-ubuntu-latest

Escaped Mutant for Mutator "Assignment": --- Original +++ New @@ @@ } $fullName = "{$name}-{$n}"; if (in_array($fullName, self::ATTRIBUTES_WITH_CONCATENATED_VALUES, true)) { - $html .= self::renderAttribute($fullName, self::encodeAttribute(is_array($v) ? implode(' ', $v) : $v)); + $html = self::renderAttribute($fullName, self::encodeAttribute(is_array($v) ? implode(' ', $v) : $v)); } else { $html .= is_array($v) ? self::renderAttribute($fullName, Json::htmlEncode($v), '\'') : self::renderAttribute($fullName, self::encodeAttribute($v)); }
$fullName,
self::encodeAttribute(
is_array($v) ? implode(' ', $v) : $v,
Expand Down Expand Up @@ -1763,7 +1714,7 @@
}
} else {
/** @var string[] */
$classes = preg_split('/\s+/', (string) $options['class'], -1, PREG_SPLIT_NO_EMPTY);

Check warning on line 1717 in src/Html.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.3-ubuntu-latest

Escaped Mutant for Mutator "CastString": --- Original +++ New @@ @@ } } else { /** @var string[] */ - $classes = preg_split('/\\s+/', (string) $options['class'], -1, PREG_SPLIT_NO_EMPTY); + $classes = preg_split('/\\s+/', $options['class'], -1, PREG_SPLIT_NO_EMPTY); $classes = array_diff($classes, (array) $class); if (empty($classes)) { unset($options['class']);
$classes = array_diff($classes, (array) $class);
if (empty($classes)) {
unset($options['class']);
Expand Down Expand Up @@ -1914,7 +1865,7 @@
public static function cssStyleToArray(string|Stringable $style): array
{
$result = [];
foreach (explode(';', (string) $style) as $property) {

Check warning on line 1868 in src/Html.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.3-ubuntu-latest

Escaped Mutant for Mutator "CastString": --- Original +++ New @@ @@ public static function cssStyleToArray(string|Stringable $style) : array { $result = []; - foreach (explode(';', (string) $style) as $property) { + foreach (explode(';', $style) as $property) { $property = explode(':', $property); if (count($property) > 1) { $result[trim($property[0])] = trim($property[1]);
$property = explode(':', $property);
if (count($property) > 1) {
$result[trim($property[0])] = trim($property[1]);
Expand Down
68 changes: 34 additions & 34 deletions tests/HtmlTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ public function testMeta(): void
{
$this->assertSame('<meta>', Html::meta()->render());
$this->assertSame(
'<meta id="main" name="keywords" content="yii">',
'<meta name="keywords" content="yii" id="main">',
Html::meta(['name' => 'keywords', 'content' => 'yii', 'id' => 'main'])->render()
);
}
Expand All @@ -154,15 +154,15 @@ public function testLink(): void
public function testCssFile(): void
{
$this->assertSame(
'<link href="http://example.com" rel="stylesheet">',
'<link rel="stylesheet" href="http://example.com">',
Html::cssFile('http://example.com')->render()
);
$this->assertSame(
'<link href rel="stylesheet">',
'<link rel="stylesheet" href>',
Html::cssFile('')->render()
);
$this->assertSame(
'<link id="main" href="http://example.com" rel="stylesheet">',
'<link rel="stylesheet" href="http://example.com" id="main">',
Html::cssFile('http://example.com', ['id' => 'main'])->render()
);
}
Expand All @@ -178,7 +178,7 @@ public function testJavaScriptFile(): void
Html::javaScriptFile('')->render()
);
$this->assertSame(
'<script id="main" src="http://example.com"></script>',
'<script src="http://example.com" id="main"></script>',
Html::javaScriptFile('http://example.com', ['id' => 'main'])->render()
);
}
Expand Down Expand Up @@ -206,7 +206,7 @@ public function testMailto(): void
Html::mailto('contact me', 'info@example.com')->render()
);
$this->assertSame(
'<a id="contact" href="mailto:info@example.com">contact me</a>',
'<a href="mailto:info@example.com" id="contact">contact me</a>',
Html::mailto('contact me', 'info@example.com', ['id' => 'contact'])->render()
);
}
Expand Down Expand Up @@ -393,7 +393,7 @@ public function testHiddenInput(): void
Html::hiddenInput('test', '43')->render(),
);
$this->assertSame(
'<input type="hidden" id="ABC" name="test" value="43">',
'<input type="hidden" name="test" value="43" id="ABC">',
Html::hiddenInput('test', '43', ['id' => 'ABC'])->render(),
);
}
Expand All @@ -417,63 +417,63 @@ public function testPasswordInput(): void
public function testFile(): void
{
$this->assertSame('<input type="file">', Html::file()->render());
$this->assertSame('<input type="file" name>', Html::file('')->render());
$this->assertSame('<input type="file" value>', Html::file(null, '')->render());
$this->assertSame('<input type="file" name="test">', Html::file('test')->render());
$this->assertSame('<input name type="file">', Html::file('')->render());
$this->assertSame('<input value type="file">', Html::file(null, '')->render());
$this->assertSame('<input name="test" type="file">', Html::file('test')->render());
$this->assertSame(
'<input type="file" name="test" value="43">',
'<input name="test" value="43" type="file">',
Html::file('test', '43')->render(),
);
$this->assertSame(
'<input type="file" class="photo" name="test" value="43">',
'<input name="test" value="43" class="photo" type="file">',
Html::file('test', '43', ['class' => 'photo'])->render(),
);
}

public function testRadio(): void
{
$this->assertSame('<input type="radio">', Html::radio()->render());
$this->assertSame('<input type="radio" name>', Html::radio('')->render());
$this->assertSame('<input type="radio" value>', Html::radio(null, '')->render());
$this->assertSame('<input type="radio" name="test">', Html::radio('test')->render());
$this->assertSame('<input name type="radio">', Html::radio('')->render());
$this->assertSame('<input value type="radio">', Html::radio(null, '')->render());
$this->assertSame('<input name="test" type="radio">', Html::radio('test')->render());
$this->assertSame(
'<input type="radio" name="test" value="43">',
'<input name="test" value="43" type="radio">',
Html::radio('test', '43')->render(),
);
$this->assertSame(
'<input type="radio" name="test" value="43" readonly>',
'<input name="test" value="43" readonly type="radio">',
Html::radio('test', '43', ['readonly' => true])->render(),
);
}

public function testCheckbox(): void
{
$this->assertSame('<input type="checkbox">', Html::checkbox()->render());
$this->assertSame('<input type="checkbox" name>', Html::checkbox('')->render());
$this->assertSame('<input type="checkbox" value>', Html::checkbox(null, '')->render());
$this->assertSame('<input type="checkbox" name="test">', Html::checkbox('test')->render());
$this->assertSame('<input name type="checkbox">', Html::checkbox('')->render());
$this->assertSame('<input value type="checkbox">', Html::checkbox(null, '')->render());
$this->assertSame('<input name="test" type="checkbox">', Html::checkbox('test')->render());
$this->assertSame(
'<input type="checkbox" name="test" value="43">',
'<input name="test" value="43" type="checkbox">',
Html::checkbox('test', '43')->render(),
);
$this->assertSame(
'<input type="checkbox" name="test" value="43" readonly>',
'<input name="test" value="43" readonly type="checkbox">',
Html::checkbox('test', '43', ['readonly' => true])->render(),
);
}

public function testRange(): void
{
$this->assertSame('<input type="range">', Html::range()->render());
$this->assertSame('<input type="range" name>', Html::range('')->render());
$this->assertSame('<input type="range" value>', Html::range(null, '')->render());
$this->assertSame('<input type="range" name="test">', Html::range('test')->render());
$this->assertSame('<input name type="range">', Html::range('')->render());
$this->assertSame('<input value type="range">', Html::range(null, '')->render());
$this->assertSame('<input name="test" type="range">', Html::range('test')->render());
$this->assertSame(
'<input type="range" name="test" value="43">',
'<input name="test" value="43" type="range">',
Html::range('test', '43')->render(),
);
$this->assertSame(
'<input type="range" name="test" value="43" readonly>',
'<input name="test" value="43" readonly type="range">',
Html::range('test', '43', ['readonly' => true])->render(),
);
}
Expand Down Expand Up @@ -516,9 +516,9 @@ public function testCheckboxList(): void
$this->assertSame(
'<input type="hidden" name="test" value="0">' . "\n" .
'<div id="main">' . "\n" .
'<label><input type="checkbox" name="test[]" value="1"> One</label>' . "\n" .
'<label><input type="checkbox" name="test[]" value="2" checked> Two</label>' . "\n" .
'<label><input type="checkbox" name="test[]" value="5" checked> Five</label>' . "\n" .
'<label><input name="test[]" value="1" type="checkbox"> One</label>' . "\n" .
'<label><input name="test[]" value="2" checked type="checkbox"> Two</label>' . "\n" .
'<label><input name="test[]" value="5" checked type="checkbox"> Five</label>' . "\n" .
'</div>',
Html::checkboxList('test')
->items([1 => 'One', 2 => 'Two', 5 => 'Five'])
Expand All @@ -534,9 +534,9 @@ public function testRadioList(): void
$this->assertSame(
'<input type="hidden" name="test" value="0">' . "\n" .
'<div id="main">' . "\n" .
'<label><input type="radio" name="test" value="1"> One</label>' . "\n" .
'<label><input type="radio" name="test" value="2" checked> Two</label>' . "\n" .
'<label><input type="radio" name="test" value="5"> Five</label>' . "\n" .
'<label><input name="test" value="1" type="radio"> One</label>' . "\n" .
'<label><input name="test" value="2" checked type="radio"> Two</label>' . "\n" .
'<label><input name="test" value="5" type="radio"> Five</label>' . "\n" .
'</div>',
Html::radioList('test')
->items([1 => 'One', 2 => 'Two', 5 => 'Five'])
Expand Down Expand Up @@ -819,7 +819,7 @@ public static function dataRenderTagAttributes(): array
[' class="first second"', ['class' => ['first', 'second']]],
['', ['class' => []]],
[' style="width: 100px; height: 200px;"', ['style' => ['width' => '100px', 'height' => '200px']]],
[' name="position" value="42"', ['value' => 42, 'name' => 'position']],
[' value="42" name="position"', ['value' => 42, 'name' => 'position']],
[
' id="x" class="a b" data-a="1" data-b="2" style="width: 100px;" any=\'[1,2]\'',
[
Expand Down
30 changes: 15 additions & 15 deletions tests/Tag/Base/BooleanInputTagTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ final class BooleanInputTagTest extends TestCase
{
public function testChecked(): void
{
$this->assertSame('<input type="test" checked>', (string)TestBooleanInputTag::tag()->checked());
$this->assertSame('<input checked type="test">', (string)TestBooleanInputTag::tag()->checked());
$this->assertSame('<input type="test">', (string)TestBooleanInputTag::tag()->checked(false));
$this->assertSame('<input type="test">', (string)TestBooleanInputTag::tag()
->checked(true)
Expand Down Expand Up @@ -64,7 +64,7 @@ public function testLabel(string $expected, ?string $label, array $attributes):
public function testLabelNoWrap(): void
{
$this->assertSame(
'<input type="test" id="ID"> <label for="ID">Voronezh</label>',
'<input id="ID" type="test"> <label for="ID">Voronezh</label>',
(string) TestBooleanInputTag::tag()
->id('ID')
->label('Voronezh', wrap: false),
Expand All @@ -74,7 +74,7 @@ public function testLabelNoWrap(): void
public function testLabelWithId(): void
{
$this->assertSame(
'<label><input type="test" id="Test"> One</label>',
'<label><input id="Test" type="test"> One</label>',
TestBooleanInputTag::tag()
->id('Test')
->label('One')
Expand All @@ -85,7 +85,7 @@ public function testLabelWithId(): void
public function testSideLabel(): void
{
$this->assertMatchesRegularExpression(
'~<input type="test" id="i(\d*?)"> <label for="i\1">One</label>~',
'~<input id="i(\d*?)" type="test"> <label for="i\1">One</label>~',
TestBooleanInputTag::tag()
->sideLabel('One')
->render()
Expand All @@ -95,7 +95,7 @@ public function testSideLabel(): void
public function testSideLabelEmpty(): void
{
$this->assertMatchesRegularExpression(
'~<input type="test" id="i(\d*?)"> <label for="i\1"></label>~',
'~<input id="i(\d*?)" type="test"> <label for="i\1"></label>~',
TestBooleanInputTag::tag()
->sideLabel('')
->render()
Expand All @@ -115,7 +115,7 @@ public function testSideLabelNull(): void
public function testSideLabelWithId(): void
{
$this->assertSame(
'<input type="test" id="Test"> <label for="Test">One</label>',
'<input id="Test" type="test"> <label for="Test">One</label>',
TestBooleanInputTag::tag()
->id('Test')
->sideLabel('One')
Expand All @@ -126,7 +126,7 @@ public function testSideLabelWithId(): void
public function testSideLabelWithAttributes(): void
{
$this->assertMatchesRegularExpression(
'~<input type="test" id="i(\d*?)"> <label class="red" for="i\1">One</label>~',
'~<input id="i(\d*?)" type="test"> <label class="red" for="i\1">One</label>~',
TestBooleanInputTag::tag()
->sideLabel('One', ['class' => 'red'])
->render()
Expand All @@ -136,7 +136,7 @@ public function testSideLabelWithAttributes(): void
public function testSideLabelId(): void
{
$this->assertSame(
'<input type="test" id="count"> <label for="count">One</label>',
'<input id="count" type="test"> <label for="count">One</label>',
TestBooleanInputTag::tag()
->sideLabel('One')
->id('count')
Expand All @@ -160,15 +160,15 @@ public static function dataUncheckValue(): array
return [
['<input type="test">', null, null],
['<input type="test">', null, 7],
['<input type="test" name="color">', 'color', null],
['<input type="test" name="color[]">', 'color[]', null],
['<input name="color" type="test">', 'color', null],
['<input name="color[]" type="test">', 'color[]', null],
[
'<input type="hidden" name="color" value="7"><input type="test" name="color">',
'<input type="hidden" name="color" value="7"><input name="color" type="test">',
'color',
7,
],
[
'<input type="hidden" name="color" value="7"><input type="test" name="color[]">',
'<input type="hidden" name="color" value="7"><input name="color[]" type="test">',
'color[]',
7,
],
Expand All @@ -191,7 +191,7 @@ public function testUncheckValueDisabled(): void
{
$this->assertSame(
'<input type="hidden" name="color" value="7" disabled>' .
'<input type="test" name="color" disabled>',
'<input name="color" disabled type="test">',
TestBooleanInputTag::tag()
->name('color')
->uncheckValue(7)
Expand All @@ -204,7 +204,7 @@ public function testUncheckValueForm(): void
{
$this->assertSame(
'<input type="hidden" name="color" value="7" form="post">' .
'<input type="test" name="color" form="post">',
'<input name="color" form="post" type="test">',
TestBooleanInputTag::tag()
->name('color')
->uncheckValue(7)
Expand All @@ -217,7 +217,7 @@ public function testUncheckValueWithLabel(): void
{
$this->assertSame(
'<input type="hidden" name="color" value="7">' .
'<label><input type="test" name="color"> Seven</label>',
'<label><input name="color" type="test"> Seven</label>',
TestBooleanInputTag::tag()
->name('color')
->uncheckValue(7)
Expand Down
6 changes: 3 additions & 3 deletions tests/Tag/Base/MediaTagTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,9 @@ public function testWrongTrackDefault(): void

$this->assertSame(
'<test>' . "\n" .
'<track src="sampleCaptions.vtt" kind="captions" srclang="en" default>' . "\n" .
'<track src="sampleDescriptions.vtt" kind="descriptions" srclang="de">' . "\n" .
'<track src="sampleChapters.vtt" kind="chapters" srclang="ja">' . "\n" .
'<track kind="captions" src="sampleCaptions.vtt" srclang="en" default>' . "\n" .
'<track kind="descriptions" src="sampleDescriptions.vtt" srclang="de">' . "\n" .
'<track kind="chapters" src="sampleChapters.vtt" srclang="ja">' . "\n" .
'</test>',
$tag->render()
);
Expand Down
4 changes: 2 additions & 2 deletions tests/Tag/Base/TagTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public static function dataAttributes(): array
['<test class="first second">', ['class' => ['first', 'second']]],
['<test>', ['class' => []]],
['<test style="width: 100px; height: 200px;">', ['style' => ['width' => '100px', 'height' => '200px']]],
['<test name="position" value="42">', ['value' => 42, 'name' => 'position']],
['<test value="42" name="position">', ['value' => 42, 'name' => 'position']],
[
'<test id="x" class="a b" data-a="1" data-b="2" style="width: 100px;" any=\'[1,2]\'>',
[
Expand Down Expand Up @@ -93,7 +93,7 @@ public function testReplaceAttributes(): void
public function testUnionAttributes(): void
{
$this->assertSame(
'<test id="color" class="red">',
'<test class="red" id="color">',
TestTag::tag()
->class('red')
->unionAttributes(['class' => 'green', 'id' => 'color'])
Expand Down
Loading
Loading