Skip to content

Commit 922c0b4

Browse files
committed
Add tests for new select option formats
1 parent f7d6b04 commit 922c0b4

File tree

2 files changed

+200
-0
lines changed

2 files changed

+200
-0
lines changed

src/Html/FormBuilder.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,21 @@ protected function setQuickTextAreaSize(array $options): array
407407

408408
/**
409409
* Create a select box field with empty option support.
410+
*
411+
* Supports several formats for the $list parameter:
412+
* - Simple format: ['value' => 'Label']
413+
* - With icon/image: ['value' => ['Label', 'icon-name']] or ['value' => ['Label', 'image.png']]
414+
* - With optgroups: ['Group Name' => ['value' => 'Label', ...]]
415+
* - Mixed format combining all of the above
416+
*
417+
* Icons are detected when the second array element doesn't contain a dot (.).
418+
* Images are detected when the second array element contains a dot (.).
419+
*
420+
* @param string $name The name attribute for the select element
421+
* @param array $list The options list (see above for supported formats)
422+
* @param string|array|null $selected The selected value(s)
423+
* @param array $options Additional HTML attributes for the select element
424+
* @return string The generated HTML select element
410425
*/
411426
public function select(string $name, array $list = [], string|array|null $selected = null, array $options = []): string
412427
{
@@ -482,6 +497,16 @@ public function selectMonth(string $name, string|array|null $selected = null, ar
482497

483498
/**
484499
* Get the select option for the given value.
500+
*
501+
* Determines whether to create a single option or an optgroup based on the $display parameter:
502+
* - If $display is an array with string keys, creates an optgroup
503+
* - If $display is an array with numeric keys (e.g., ['Label', 'icon']), creates a single option with icon/image
504+
* - If $display is a string, creates a simple option
505+
*
506+
* @param string|array $display The display value or array for optgroup/icon/image
507+
* @param string $value The option value attribute
508+
* @param string|array|null $selected The selected value(s)
509+
* @return string The generated HTML option or optgroup element
485510
*/
486511
public function getSelectOption(string|array $display, string $value, string|array|null $selected = null): string
487512
{
@@ -511,6 +536,15 @@ protected function optionGroup(array $list, string $label, string|array|null $se
511536

512537
/**
513538
* Create a select element option.
539+
*
540+
* If $display is an array in the format ['Label', 'icon-or-image'], adds data attributes:
541+
* - data-icon: added if the second element doesn't contain a dot (e.g., 'icon-refresh')
542+
* - data-image: added if the second element contains a dot (e.g., 'image.png')
543+
*
544+
* @param string|array $display The display label or array with label and icon/image
545+
* @param string $value The option value attribute
546+
* @param string|array|null $selected The selected value(s)
547+
* @return string The generated HTML option element
514548
*/
515549
protected function option(string|array $display, string $value, string|array|null $selected = null): string
516550
{

tests/Html/FormBuilderTest.php

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,4 +340,170 @@ public function testSelectWithEmptyOption()
340340
$this->assertStringContainsString('<option value="1">Option 1</option>', $result);
341341
$this->assertStringContainsString('<option value="2">Option 2</option>', $result);
342342
}
343+
344+
/**
345+
* @testdox can create a select element with icon data attributes.
346+
*/
347+
public function testSelectWithIcon()
348+
{
349+
$result = $this->formBuilder->select(
350+
name: 'my-select',
351+
list: [
352+
'1' => 'Regular Option',
353+
'2' => ['Option With Icon', 'icon-refresh'],
354+
],
355+
selected: null,
356+
options: []
357+
);
358+
359+
$this->assertElementIs('select', $result);
360+
$this->assertElementAttributeEquals('name', 'my-select', $result);
361+
$this->assertStringContainsString('<option value="1">Regular Option</option>', $result);
362+
$this->assertStringContainsString('<option value="2" data-icon="icon-refresh">Option With Icon</option>', $result);
363+
}
364+
365+
/**
366+
* @testdox can create a select element with image data attributes.
367+
*/
368+
public function testSelectWithImage()
369+
{
370+
$result = $this->formBuilder->select(
371+
name: 'my-select',
372+
list: [
373+
'1' => 'Regular Option',
374+
'2' => ['Option With Image', 'myImage.jpeg'],
375+
],
376+
selected: null,
377+
options: []
378+
);
379+
380+
$this->assertElementIs('select', $result);
381+
$this->assertElementAttributeEquals('name', 'my-select', $result);
382+
$this->assertStringContainsString('<option value="1">Regular Option</option>', $result);
383+
$this->assertStringContainsString('<option value="2" data-image="myImage.jpeg">Option With Image</option>', $result);
384+
}
385+
386+
/**
387+
* @testdox can create a select element with optgroups.
388+
*/
389+
public function testSelectWithOptgroups()
390+
{
391+
$result = $this->formBuilder->select(
392+
name: 'my-select',
393+
list: [
394+
'Group 1' => [
395+
'g1-opt1' => 'Group 1 Option 1',
396+
'g1-opt2' => 'Group 1 Option 2',
397+
],
398+
'Group 2' => [
399+
'g2-opt1' => 'Group 2 Option 1',
400+
'g2-opt2' => 'Group 2 Option 2',
401+
],
402+
],
403+
selected: null,
404+
options: []
405+
);
406+
407+
$this->assertElementIs('select', $result);
408+
$this->assertElementAttributeEquals('name', 'my-select', $result);
409+
$this->assertStringContainsString('<optgroup label="Group 1">', $result);
410+
$this->assertStringContainsString('<optgroup label="Group 2">', $result);
411+
$this->assertStringContainsString('<option value="g1-opt1">Group 1 Option 1</option>', $result);
412+
$this->assertStringContainsString('<option value="g1-opt2">Group 1 Option 2</option>', $result);
413+
$this->assertStringContainsString('<option value="g2-opt1">Group 2 Option 1</option>', $result);
414+
$this->assertStringContainsString('<option value="g2-opt2">Group 2 Option 2</option>', $result);
415+
$this->assertStringContainsString('</optgroup>', $result);
416+
}
417+
418+
/**
419+
* @testdox can create a select element with optgroups containing icons and images.
420+
*/
421+
public function testSelectWithOptgroupsAndIconsImages()
422+
{
423+
$result = $this->formBuilder->select(
424+
name: 'my-select',
425+
list: [
426+
'option1' => 'Regular option',
427+
'option2' => ['Option With Image', 'myImage.jpeg'],
428+
'Group1' => [
429+
'group1-opt1' => 'OptGroup Option1 regular option',
430+
'group1-opt2' => ['OptGroup Option2 with icon', 'icon-refresh'],
431+
'group1-opt3' => ['OptGroup Option3 with image', 'otherImage.png'],
432+
],
433+
'Group2' => [
434+
'group2-opt1' => 'OptGroup2 Option1',
435+
'group2-opt2' => 'OptGroup2 Option2',
436+
],
437+
],
438+
selected: null,
439+
options: []
440+
);
441+
442+
$this->assertElementIs('select', $result);
443+
$this->assertElementAttributeEquals('name', 'my-select', $result);
444+
445+
// Regular options
446+
$this->assertStringContainsString('<option value="option1">Regular option</option>', $result);
447+
$this->assertStringContainsString('<option value="option2" data-image="myImage.jpeg">Option With Image</option>', $result);
448+
449+
// Optgroups
450+
$this->assertStringContainsString('<optgroup label="Group1">', $result);
451+
$this->assertStringContainsString('<optgroup label="Group2">', $result);
452+
453+
// Options inside optgroups
454+
$this->assertStringContainsString('<option value="group1-opt1">OptGroup Option1 regular option</option>', $result);
455+
$this->assertStringContainsString('<option value="group1-opt2" data-icon="icon-refresh">OptGroup Option2 with icon</option>', $result);
456+
$this->assertStringContainsString('<option value="group1-opt3" data-image="otherImage.png">OptGroup Option3 with image</option>', $result);
457+
$this->assertStringContainsString('<option value="group2-opt1">OptGroup2 Option1</option>', $result);
458+
$this->assertStringContainsString('<option value="group2-opt2">OptGroup2 Option2</option>', $result);
459+
}
460+
461+
/**
462+
* @testdox can create a select element with backward compatibility for simple string options.
463+
*/
464+
public function testSelectBackwardCompatibility()
465+
{
466+
$result = $this->formBuilder->select(
467+
name: 'my-select',
468+
list: [
469+
'1' => 'Option 1',
470+
'2' => 'Option 2',
471+
'3' => 'Option 3',
472+
],
473+
selected: '2',
474+
options: []
475+
);
476+
477+
$this->assertElementIs('select', $result);
478+
$this->assertElementAttributeEquals('name', 'my-select', $result);
479+
$this->assertStringContainsString('<option value="1">Option 1</option>', $result);
480+
$this->assertStringContainsString('<option value="2" selected="selected">Option 2</option>', $result);
481+
$this->assertStringContainsString('<option value="3">Option 3</option>', $result);
482+
$this->assertStringNotContainsString('data-icon', $result);
483+
$this->assertStringNotContainsString('data-image', $result);
484+
}
485+
486+
/**
487+
* @testdox properly escapes HTML in option labels and values.
488+
*/
489+
public function testSelectHtmlEscaping()
490+
{
491+
$result = $this->formBuilder->select(
492+
name: 'my-select',
493+
list: [
494+
'<script>' => 'Normal Label',
495+
'safe-value' => ['<b>Bold Label</b>', 'icon-test'],
496+
],
497+
selected: null,
498+
options: []
499+
);
500+
501+
$this->assertElementIs('select', $result);
502+
// The value attribute is escaped by the HtmlBuilder, resulting in double encoding
503+
$this->assertStringContainsString('value="&amp;lt;script&amp;gt;"', $result);
504+
$this->assertStringContainsString('&lt;b&gt;Bold Label&lt;/b&gt;', $result);
505+
// Ensure dangerous tags are not rendered as raw HTML
506+
$this->assertStringNotContainsString('value="<script>"', $result);
507+
$this->assertStringNotContainsString('<b>Bold Label</b>', $result);
508+
}
343509
}

0 commit comments

Comments
 (0)