Skip to content

Commit 94c25e7

Browse files
authored
Added tests for Radio and Clear button, Badge and Choice input (#47)
* Added tests for Radio and Clear button, Badge and Choice input * Added tests for checkbox field
1 parent 7c3880f commit 94c25e7

File tree

7 files changed

+788
-0
lines changed

7 files changed

+788
-0
lines changed
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) Ibexa AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
declare(strict_types=1);
8+
9+
namespace Ibexa\Tests\Integration\DesignSystemTwig\Twig\Components;
10+
11+
use Ibexa\DesignSystemTwig\Twig\Components\Badge;
12+
use PHPUnit\Framework\Attributes\DataProvider;
13+
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
14+
use Symfony\Component\DomCrawler\Crawler;
15+
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
16+
use Symfony\UX\TwigComponent\Test\InteractsWithTwigComponents;
17+
18+
final class BadgeTest extends KernelTestCase
19+
{
20+
use InteractsWithTwigComponents;
21+
22+
public function testMount(): void
23+
{
24+
$component = $this->mountTwigComponent(Badge::class, [
25+
'size' => 'small',
26+
'value' => 7,
27+
'maxBadgeValue' => 15,
28+
]);
29+
30+
self::assertInstanceOf(Badge::class, $component, 'Component should mount as Badge.');
31+
self::assertSame('small', $component->size, 'Prop "size" should be "small".');
32+
self::assertSame(7, $component->value, 'Prop "value" should be 7.');
33+
self::assertSame(15, $component->maxBadgeValue, 'Prop "maxBadgeValue" should be 15.');
34+
}
35+
36+
public function testDefaultRender(): void
37+
{
38+
$crawler = $this->renderTwigComponent(Badge::class, ['value' => 1])->crawler();
39+
$badge = $this->getBadge($crawler);
40+
$class = $this->getClassAttr($badge);
41+
42+
self::assertStringContainsString('ids-badge', $class, 'Base class "ids-badge" should be present.');
43+
self::assertStringNotContainsString('ids-badge--small', $class, 'Default size should be "medium", not "small".');
44+
self::assertStringNotContainsString('ids-badge--stretched', $class, 'With value below threshold, stretched modifier should not be present.');
45+
self::assertSame('1', $this->getText($badge), 'Badge text should equal value when under the cap.');
46+
self::assertSame('99', $badge->attr('data-ids-max-badge-value'), 'Default max should be 99.');
47+
}
48+
49+
public function testSizeVariantSmallAddsClass(): void
50+
{
51+
$crawler = $this->renderTwigComponent(Badge::class, ['size' => 'small', 'value' => 1])->crawler();
52+
$badge = $this->getBadge($crawler);
53+
$class = $this->getClassAttr($badge);
54+
55+
self::assertStringContainsString('ids-badge--small', $class, 'Small size should add "ids-badge--small" class.');
56+
}
57+
58+
/**
59+
* @param non-empty-string $size
60+
*/
61+
#[DataProvider('stretchedProvider')]
62+
public function testStretchedModifier(string $size, int $value, bool $expected): void
63+
{
64+
$crawler = $this->renderTwigComponent(Badge::class, ['size' => $size, 'value' => $value])->crawler();
65+
$badge = $this->getBadge($crawler);
66+
$class = $this->getClassAttr($badge);
67+
68+
if ($expected) {
69+
self::assertStringContainsString('ids-badge--stretched', $class, 'Expected stretched modifier to be present.');
70+
} else {
71+
self::assertStringNotContainsString('ids-badge--stretched', $class, 'Expected stretched modifier to be absent.');
72+
}
73+
}
74+
75+
/**
76+
* @return iterable<string, array{0: string, 1: int, 2: bool}>
77+
*/
78+
public static function stretchedProvider(): iterable
79+
{
80+
yield 'medium below' => ['medium', 99, false];
81+
yield 'medium at' => ['medium', 100, true];
82+
yield 'small below' => ['small', 9, false];
83+
yield 'small at' => ['small', 10, true];
84+
}
85+
86+
public function testFormattedValueIsCappedByMax(): void
87+
{
88+
$crawler = $this->renderTwigComponent(Badge::class, [
89+
'value' => 150,
90+
'maxBadgeValue' => 99,
91+
])->crawler();
92+
93+
$badge = $this->getBadge($crawler);
94+
self::assertSame('99+', $this->getText($badge), 'When value exceeds maxBadgeValue, text should display "<max>+".');
95+
self::assertSame('99', $badge->attr('data-ids-max-badge-value'), 'data-ids-max-badge-value should match the exposed "max_value".');
96+
}
97+
98+
public function testFormattedValueDisplaysRawValueWhenUnderMax(): void
99+
{
100+
$crawler = $this->renderTwigComponent(Badge::class, [
101+
'value' => 42,
102+
'maxBadgeValue' => 99,
103+
])->crawler();
104+
105+
$badge = $this->getBadge($crawler);
106+
self::assertSame('42', $this->getText($badge), 'When value <= maxBadgeValue, text should display the raw value.');
107+
}
108+
109+
public function testInvalidPropsCauseResolverErrorOnMount(): void
110+
{
111+
$this->expectException(InvalidOptionsException::class);
112+
$this->mountTwigComponent(Badge::class, ['size' => 'giant', 'value' => 1]);
113+
}
114+
115+
public function testInvalidValueTypeCauseResolverErrorOnMount(): void
116+
{
117+
$this->expectException(InvalidOptionsException::class);
118+
119+
$this->mountTwigComponent(Badge::class, ['value' => 'not-int']);
120+
}
121+
122+
private function getBadge(Crawler $crawler): Crawler
123+
{
124+
$node = $crawler->filter('div.ids-badge')->first();
125+
self::assertGreaterThan(0, $node->count(), 'Badge wrapper ".ids-badge" should be present.');
126+
127+
return $node;
128+
}
129+
130+
private function getClassAttr(Crawler $node): string
131+
{
132+
return (string) $node->attr('class');
133+
}
134+
135+
private function getText(Crawler $node): string
136+
{
137+
return trim($node->text(''));
138+
}
139+
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) Ibexa AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
declare(strict_types=1);
8+
9+
namespace Ibexa\Tests\Integration\DesignSystemTwig\Twig\Components\Checkbox;
10+
11+
use Ibexa\DesignSystemTwig\Twig\Components\Checkbox\Field;
12+
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
13+
use Symfony\Component\DomCrawler\Crawler;
14+
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
15+
use Symfony\Component\OptionsResolver\Exception\MissingOptionsException;
16+
use Symfony\UX\TwigComponent\Test\InteractsWithTwigComponents;
17+
18+
final class FieldTest extends KernelTestCase
19+
{
20+
use InteractsWithTwigComponents;
21+
22+
public function testMount(): void
23+
{
24+
$component = $this->mountTwigComponent(Field::class, $this->baseProps([
25+
'checked' => true,
26+
'disabled' => true,
27+
'required' => true,
28+
'indeterminate' => true,
29+
]));
30+
31+
self::assertInstanceOf(Field::class, $component, 'Component should mount as Checkbox\\Field.');
32+
}
33+
34+
public function testDefaultRenderProducesWrapperLabelAndInput(): void
35+
{
36+
$crawler = $this->renderTwigComponent(
37+
Field::class,
38+
$this->baseProps(),
39+
'Accept terms'
40+
)->crawler();
41+
42+
$wrapper = $this->getWrapper($crawler);
43+
$classes = $this->getClassAttr($wrapper);
44+
self::assertStringContainsString('ids-choice-input-field', $classes, 'Wrapper should include base "ids-choice-input-field".');
45+
self::assertStringContainsString('ids-checkbox-field', $classes, 'Wrapper should include variant "ids-checkbox-field".');
46+
47+
$label = $this->getLabel($crawler);
48+
self::assertSame('agree', $label->attr('for'), 'Label "for" should equal the top-level id.');
49+
self::assertStringContainsString('Accept terms', $this->getText($label), 'Label should render slot content.');
50+
51+
$input = $this->getCheckbox($crawler);
52+
self::assertSame('checkbox', $input->attr('type'), 'Input "type" should be "checkbox".');
53+
self::assertSame('agree', $input->attr('id'), 'Input "id" should equal provided id.');
54+
self::assertSame('consent', $input->attr('name'), 'Input "name" should equal provided name.');
55+
}
56+
57+
public function testWrapperAttributesMergeClassAndData(): void
58+
{
59+
$crawler = $this->renderTwigComponent(
60+
Field::class,
61+
$this->baseProps([
62+
'attributes' => ['class' => 'extra-class', 'data-custom' => 'custom'],
63+
]),
64+
'Label'
65+
)->crawler();
66+
67+
$wrapper = $this->getWrapper($crawler);
68+
$classes = $this->getClassAttr($wrapper);
69+
self::assertStringContainsString('extra-class', $classes, 'Custom class should be merged on the wrapper.');
70+
self::assertSame('custom', $wrapper->attr('data-custom'), 'Custom data attribute should render on the wrapper.');
71+
}
72+
73+
public function testBooleanFlagsRenderNativeAttributesAndIndeterminateClass(): void
74+
{
75+
$crawler = $this->renderTwigComponent(
76+
Field::class,
77+
$this->baseProps([
78+
'checked' => true,
79+
'disabled' => true,
80+
'required' => true,
81+
'indeterminate' => true,
82+
]),
83+
'Label'
84+
)->crawler();
85+
86+
$input = $this->getCheckbox($crawler);
87+
88+
self::assertNotNull($input->attr('disabled'), 'disabled=true should render native "disabled" attribute.');
89+
self::assertNotNull($input->attr('required'), 'required=true should render native "required" attribute.');
90+
self::assertNotNull($input->attr('checked'), 'checked=true should render native "checked" attribute.');
91+
92+
self::assertStringContainsString('ids-input--indeterminate', $this->getClassAttr($input), 'indeterminate=true should add "ids-input--indeterminate" class.');
93+
}
94+
95+
public function testInvalidIndeterminateTypeCausesResolverErrorOnMount(): void
96+
{
97+
$this->expectException(InvalidOptionsException::class);
98+
99+
$this->mountTwigComponent(Field::class, $this->baseProps([
100+
'indeterminate' => 'not-bool',
101+
]));
102+
}
103+
104+
public function testMissingRequiredOptionsCauseResolverErrorOnMount(): void
105+
{
106+
$this->expectException(MissingOptionsException::class);
107+
108+
$this->mountTwigComponent(Field::class, [
109+
'value' => 'yes',
110+
]);
111+
}
112+
113+
/**
114+
* @param array<string, mixed> $overrides
115+
*
116+
* @return array<string, mixed>
117+
*/
118+
private function baseProps(array $overrides = []): array
119+
{
120+
return array_replace([
121+
'id' => 'agree',
122+
'name' => 'consent',
123+
'value' => 'yes',
124+
], $overrides);
125+
}
126+
127+
private function getWrapper(Crawler $crawler): Crawler
128+
{
129+
$node = $crawler->filter('.ids-choice-input-field')->first();
130+
self::assertGreaterThan(0, $node->count(), 'Wrapper ".ids-choice-input-field" should be present.');
131+
132+
return $node;
133+
}
134+
135+
private function getLabel(Crawler $crawler): Crawler
136+
{
137+
$node = $crawler->filter('.ids-choice-input-field .ids-choice-input-label')->first();
138+
self::assertGreaterThan(0, $node->count(), 'Choice input label should be present.');
139+
140+
return $node;
141+
}
142+
143+
private function getCheckbox(Crawler $crawler): Crawler
144+
{
145+
$node = $crawler->filter('input[type="checkbox"]')->first();
146+
self::assertGreaterThan(0, $node->count(), 'Checkbox input should be present.');
147+
148+
return $node;
149+
}
150+
151+
private function getClassAttr(Crawler $node): string
152+
{
153+
return (string) $node->attr('class');
154+
}
155+
156+
private function getText(Crawler $node): string
157+
{
158+
return trim($node->text(''));
159+
}
160+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) Ibexa AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
declare(strict_types=1);
8+
9+
namespace Ibexa\Tests\Integration\DesignSystemTwig\Twig\Components;
10+
11+
use Ibexa\DesignSystemTwig\Twig\Components\ChoiceInputLabel;
12+
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
13+
use Symfony\Component\DomCrawler\Crawler;
14+
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
15+
use Symfony\UX\TwigComponent\Test\InteractsWithTwigComponents;
16+
17+
final class ChoiceInputLabelTest extends KernelTestCase
18+
{
19+
use InteractsWithTwigComponents;
20+
21+
public function testMount(): void
22+
{
23+
$component = $this->mountTwigComponent(
24+
ChoiceInputLabel::class,
25+
[
26+
'for' => 'agree',
27+
'content' => 'I agree to terms',
28+
'attributes' => ['class' => 'extra-class', 'data-custom' => 'custom'],
29+
]
30+
);
31+
32+
self::assertInstanceOf(ChoiceInputLabel::class, $component, 'Component should mount as ChoiceInputLabel.');
33+
self::assertSame('agree', $component->for, 'Prop "for" should be set.');
34+
self::assertSame('I agree to terms', $component->content, 'Prop "content" should be set.');
35+
}
36+
37+
public function testDefaultRenderWithForAndContent(): void
38+
{
39+
$crawler = $this->renderTwigComponent(
40+
ChoiceInputLabel::class,
41+
['for' => 'newsletter', 'content' => 'Subscribe me']
42+
)->crawler();
43+
44+
$label = $this->getLabel($crawler);
45+
46+
self::assertSame('newsletter', $label->attr('for'), 'Rendered label "for" should equal provided id.');
47+
self::assertSame('Subscribe me', $this->getText($label), 'Rendered label should contain provided content.');
48+
49+
$class = $this->getClassAttr($label);
50+
self::assertStringContainsString('ids-choice-input-label', $class, 'Base class "ids-choice-input-label" should be present.');
51+
}
52+
53+
public function testAttributesMergeClassAndData(): void
54+
{
55+
$crawler = $this->renderTwigComponent(
56+
ChoiceInputLabel::class,
57+
[
58+
'for' => 'agree',
59+
'content' => 'I agree',
60+
'attributes' => [
61+
'class' => 'extra-class',
62+
'data-custom' => 'custom',
63+
],
64+
]
65+
)->crawler();
66+
67+
$label = $this->getLabel($crawler);
68+
$class = $this->getClassAttr($label);
69+
70+
self::assertStringContainsString('extra-class', $class, 'Custom class should be merged into label class attribute.');
71+
self::assertSame('custom', $label->attr('data-custom'), 'Custom data attribute should be rendered on the label.');
72+
}
73+
74+
public function testInvalidForTypeCausesResolverErrorOnMount(): void
75+
{
76+
$this->expectException(InvalidOptionsException::class);
77+
78+
$this->mountTwigComponent(ChoiceInputLabel::class, ['for' => 123, 'content' => 'x']);
79+
}
80+
81+
private function getLabel(Crawler $crawler): Crawler
82+
{
83+
$node = $crawler->filter('label.ids-choice-input-label')->first();
84+
self::assertGreaterThan(0, $node->count(), 'Label wrapper "label.ids-choice-input-label" should be present.');
85+
86+
return $node;
87+
}
88+
89+
private function getClassAttr(Crawler $node): string
90+
{
91+
return (string) $node->attr('class');
92+
}
93+
94+
private function getText(Crawler $node): string
95+
{
96+
return trim($node->text(''));
97+
}
98+
}

0 commit comments

Comments
 (0)