Skip to content

Commit 077cc7d

Browse files
authored
fix(view): improved attribute precedence (#1168)
1 parent ce06b52 commit 077cc7d

File tree

3 files changed

+109
-14
lines changed

3 files changed

+109
-14
lines changed

src/Tempest/View/src/Elements/IsElement.php

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,21 +34,17 @@ public function getAttributes(): array
3434

3535
$attributes = [...$this->attributes, ...$wrappingAttributes];
3636

37-
// Some attributes should always come after others,
38-
// so that these expressions have access to the data attributes
39-
$attributePrecedence = [
40-
':foreach' => 1,
41-
':if' => 2,
42-
];
37+
$tailingAttributes = [];
4338

44-
uksort($attributes, function (string $a, string $b) use ($attributePrecedence) {
45-
$precedenceA = $attributePrecedence[$a] ?? 0;
46-
$precedenceB = $attributePrecedence[$b] ?? 0;
47-
48-
return $precedenceA <=> $precedenceB;
49-
});
39+
foreach ($attributes as $name => $value) {
40+
if ($name === ':foreach' || $name === ':if') {
41+
unset($attributes[$name]);
42+
$tailingAttributes[$name] = $value;
43+
}
44+
}
5045

51-
return $attributes;
46+
// Tailing attributes are reversed because they need to be applied in reverse order
47+
return [...$attributes, ...array_reverse($tailingAttributes)];
5248
}
5349

5450
public function hasAttribute(string $name): bool

tests/Integration/View/TempestViewRendererCombinedExpressionsTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public function test_foreach_with_if_and_else_expression(): void
7676
public function test_foreach_with_if_and_forelse_expression(): void
7777
{
7878
$view = <<<'HTML'
79-
<div :foreach="$items as $item" :if="$label ?? null">
79+
<div :if="$label ?? null" :foreach="$items as $item">
8080
{{ $label }} {{ $item }}
8181
</div>
8282
<span :forelse>

tests/Integration/View/TempestViewRendererTest.php

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,105 @@ public function test_loop_variable_can_be_used_within_the_looped_tag(): void
604604
HTML, $html);
605605
}
606606

607+
public function test_if_and_foreach_precedence(): void
608+
{
609+
$html = $this->render(
610+
<<<'HTML'
611+
<div :foreach="$items as $item" :if="$item->show">{{ $item->name }}</div>
612+
HTML,
613+
items: [
614+
(object) ['name' => 'A', 'show' => true],
615+
(object) ['name' => 'B', 'show' => false],
616+
(object) ['name' => 'C', 'show' => true],
617+
],
618+
);
619+
620+
$this->assertSnippetsMatch('<div>A</div><div>C</div>', $html);
621+
622+
$html = $this->render(
623+
<<<'HTML'
624+
<div :foreach="$items as $item" :if="$show">{{ $item->name }}</div>
625+
HTML,
626+
show: true,
627+
items: [
628+
(object) ['name' => 'A', 'show' => true],
629+
(object) ['name' => 'B', 'show' => false],
630+
(object) ['name' => 'C', 'show' => true],
631+
],
632+
);
633+
634+
$this->assertSnippetsMatch('<div>A</div><div>B</div><div>C</div>', $html);
635+
636+
$html = $this->render(
637+
<<<'HTML'
638+
<div :if="$show" :foreach="$items as $item">{{ $item->name }}</div>
639+
HTML,
640+
show: true,
641+
items: [
642+
(object) ['name' => 'A', 'show' => true],
643+
(object) ['name' => 'B', 'show' => false],
644+
(object) ['name' => 'C', 'show' => true],
645+
],
646+
);
647+
648+
$this->assertSnippetsMatch('<div>A</div><div>B</div><div>C</div>', $html);
649+
650+
$html = $this->render(
651+
<<<'HTML'
652+
<div :foreach="$items as $item" :if="$show">{{ $item->name }}</div>
653+
HTML,
654+
show: false,
655+
items: [
656+
(object) ['name' => 'A', 'show' => true],
657+
(object) ['name' => 'B', 'show' => false],
658+
(object) ['name' => 'C', 'show' => true],
659+
],
660+
);
661+
662+
$this->assertSnippetsMatch('', $html);
663+
664+
$html = $this->render(
665+
<<<'HTML'
666+
<div :if="$show" :foreach="$items as $item">{{ $item->name }}</div>
667+
HTML,
668+
show: false,
669+
items: [
670+
(object) ['name' => 'A', 'show' => true],
671+
(object) ['name' => 'B', 'show' => false],
672+
(object) ['name' => 'C', 'show' => true],
673+
],
674+
);
675+
676+
$this->assertSnippetsMatch('', $html);
677+
678+
$html = $this->render(
679+
<<<'HTML'
680+
<div :if="$item->show" :foreach="$items as $item">{{ $item->name }}</div>
681+
HTML,
682+
item: (object) ['show' => true],
683+
items: [
684+
(object) ['name' => 'A', 'show' => true],
685+
(object) ['name' => 'B', 'show' => false],
686+
(object) ['name' => 'C', 'show' => true],
687+
],
688+
);
689+
690+
$this->assertSnippetsMatch('<div>A</div><div>B</div><div>C</div>', $html);
691+
692+
$html = $this->render(
693+
<<<'HTML'
694+
<div :if="$item->show ?? null" :foreach="$items as $item">{{ $item->name }}</div>
695+
HTML,
696+
items: [
697+
(object) ['name' => 'A', 'show' => true],
698+
(object) ['name' => 'B', 'show' => false],
699+
(object) ['name' => 'C', 'show' => true],
700+
],
701+
);
702+
703+
$this->assertSnippetsMatch('', $html);
704+
}
705+
607706
private function assertSnippetsMatch(string $expected, string $actual): void
608707
{
609708
$expected = str_replace([PHP_EOL, ' '], '', $expected);

0 commit comments

Comments
 (0)