@@ -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="&lt;script&gt;" ' , $ result );
504+ $ this ->assertStringContainsString ('<b>Bold Label</b> ' , $ 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