ui_icons-1.0.x-dev/tests/src/Unit/Element/IconAutocompleteUnitTest.php

tests/src/Unit/Element/IconAutocompleteUnitTest.php
<?php

declare(strict_types=1);

namespace Drupal\Tests\ui_icons\Unit\Element;

// cspell:ignore corge quux
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Theme\Icon\IconDefinition;
use Drupal\Core\Theme\Icon\IconDefinitionInterface;
use Drupal\Core\Theme\Icon\Plugin\IconPackManagerInterface;
use Drupal\Tests\Core\Theme\Icon\IconTestTrait;
use Drupal\Tests\UnitTestCase;
use Drupal\ui_icons\Element\IconAutocomplete;
use Drupal\ui_icons\IconSearch;
use Prophecy\Argument;
use Symfony\Component\HttpFoundation\Request;

/**
 * @coversDefaultClass \Drupal\ui_icons\Element\IconAutocomplete
 *
 * @group ui_icons
 */
class IconAutocompleteUnitTest extends UnitTestCase {

  use IconTestTrait;

  /**
   * The container.
   *
   * @var \Drupal\Core\DependencyInjection\ContainerBuilder
   */
  private ContainerBuilder $container;

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();

    $this->container = new ContainerBuilder();
    $this->container->set('plugin.manager.icon_pack', $this->createMock(IconPackManagerInterface::class));
    $this->container->set('string_translation', $this->getStringTranslationStub());
    \Drupal::setContainer($this->container);
  }

  /**
   * Test the getInfo method.
   */
  public function testGetInfo(): void {
    $iconAutocomplete = new IconAutocomplete([], 'test', 'test');
    $info = $iconAutocomplete->getInfo();

    $class = 'Drupal\ui_icons\Element\IconAutocomplete';
    $expected = [
      '#input' => TRUE,
      '#element_validate' => [
        [$class, 'validateIcon'],
      ],
      '#process' => [
        [$class, 'processIcon'],
        [$class, 'processIconAjaxForm'],
        [$class, 'processAjaxForm'],
        [$class, 'processGroup'],
      ],
      '#pre_render' => [
        [$class, 'preRenderGroup'],
      ],
      '#theme' => 'icon_selector',
      '#theme_wrappers' => ['form_element'],
      '#allowed_icon_pack' => [],
      '#result_format' => 'list',
      '#max_result' => IconSearch::SEARCH_RESULT,
      '#show_settings' => FALSE,
      '#default_settings' => [],
    ];

    foreach ($expected as $key => $value) {
      $this->assertArrayHasKey($key, $info);
      $this->assertSame($value, $info[$key]);
    }
  }

  /**
   * Test the processIcon method.
   */
  public function testProcessIcon(): void {
    $form_state = $this->createMock('Drupal\Core\Form\FormState');
    $complete_form = [];

    // phpcs:disable
    $value = new class implements \Stringable {function __toString() {
        return 'foo';
      }
    };
    // phpcs:enable

    $element = [
      '#parents' => ['foo', 'bar'],
      '#array_parents' => ['baz', 'qux'],
      '#value' => $value,
    ];

    IconAutocomplete::processIcon($element, $form_state, $complete_form);

    $expected = [
      '#parents' => ['foo', 'bar'],
      '#array_parents' => ['baz', 'qux'],
      '#tree' => TRUE,
      '#value' => [],
      'icon_id' => [
        '#type' => 'search',
        '#title' => new TranslatableMarkup('Icon'),
        '#description' => new TranslatableMarkup('Start typing the icon name. Icon availability depends on the selected icon packs.'),
        '#placeholder' => '',
        '#title_display' => 'invisible',
        '#autocomplete_route_name' => 'ui_icons.autocomplete',
        '#required' => FALSE,
        '#size' => 55,
        '#maxlength' => 128,
        '#value' => '',
        '#limit_validation_errors' => [$element['#parents']],
      ],
    ];

    $this->assertEquals($expected, $element);

    unset($element['#value'], $expected['#value']);

    // Test basic values and #default_value.
    $values = [
      '#size' => 22,
      '#placeholder' => new TranslatableMarkup('Qux'),
      '#required' => TRUE,
      '#default_value' => 'foo:bar',
    ];
    $element += $values;

    IconAutocomplete::processIcon($element, $form_state, $complete_form);

    $expected['#required'] = $values['#required'];
    $expected['#default_value'] = $values['#default_value'];
    $expected['icon_id']['#size'] = $values['#size'];
    $expected['icon_id']['#placeholder'] = $values['#placeholder'];
    $expected['icon_id']['#required'] = $values['#required'];
    $expected['icon_id']['#value'] = $values['#default_value'];

    $this->assertEquals($expected, $element);

    // Test value set used before default.
    $element['#value']['icon_id'] = 'baz:qux';
    IconAutocomplete::processIcon($element, $form_state, $complete_form);

    $this->assertSame('baz:qux', $element['icon_id']['#value']);

    // Test empty allowed with basic values and default value.
    $element['#allowed_icon_pack'] = [];
    IconAutocomplete::processIcon($element, $form_state, $complete_form);

    $this->assertArrayNotHasKey('#autocomplete_query_parameters', $element['icon_id']);

    // Test allowed.
    $element['#allowed_icon_pack'] = ['corge', 'quux'];
    IconAutocomplete::processIcon($element, $form_state, $complete_form);

    $this->assertArrayHasKey('allowed_icon_pack', $element['icon_id']['#autocomplete_query_parameters']);
    $this->assertSame('corge+quux', $element['icon_id']['#autocomplete_query_parameters']['allowed_icon_pack']);

    // Test search format and result.
    $element['#max_result'] = 666;
    $element['#result_format'] = 'grid';
    IconAutocomplete::processIcon($element, $form_state, $complete_form);

    $this->assertArrayHasKey('max_result', $element['icon_id']['#autocomplete_query_parameters']);
    $this->assertSame(666, $element['icon_id']['#autocomplete_query_parameters']['max_result']);
    $this->assertArrayHasKey('result_format', $element['icon_id']['#autocomplete_query_parameters']);
    $this->assertSame('grid', $element['icon_id']['#autocomplete_query_parameters']['result_format']);

    // Test values are cleaned on the parent element.
    $this->assertArrayNotHasKey('#size', $element);
    $this->assertArrayNotHasKey('#placeholder', $element);

    // Ensure we still have no settings.
    $this->assertArrayNotHasKey('icon_settings', $element);
  }

  /**
   * Test the processIconAjaxForm method.
   */
  public function testProcessIconAjaxForm(): void {
    $form_state = $this->createMock('Drupal\Core\Form\FormState');
    $complete_form = [];

    $base_element = [
      '#parents' => ['foo', 'bar'],
      '#array_parents' => ['baz', 'qux'],
      '#show_settings' => FALSE,
      '#settings_title' => new TranslatableMarkup('Baz'),
    ];

    $element = $base_element;
    IconAutocomplete::processIcon($element, $form_state, $complete_form);
    IconAutocomplete::processIconAjaxForm($element, $form_state, $complete_form);
    $this->assertArrayHasKey('#ajax', $element['icon_id']);
    $this->assertArrayNotHasKey('icon_settings', $element);

    // Test show settings without icon id.
    $element = $base_element;
    $element['#show_settings'] = TRUE;
    IconAutocomplete::processIcon($element, $form_state, $complete_form);
    IconAutocomplete::processIconAjaxForm($element, $form_state, $complete_form);
    $this->assertArrayHasKey('#ajax', $element['icon_id']);
    $this->assertArrayNotHasKey('icon_settings', $element);

    // Test settings enabled with icon_id.
    $ui_icon_pack_plugin_manager = $this->createMock(IconPackManagerInterface::class);
    $ui_icon_pack_plugin_manager->expects($this->once())->method('getIcon')->willReturn($this->createMockIcon());
    $ui_icon_pack_plugin_manager->expects($this->once())
      ->method('getExtractorPluginForms')
      ->with($this->anything())
      ->willReturnCallback(function (&$form): void {
        $form['sub_form'] = TRUE;
      });
    $this->container->set('plugin.manager.icon_pack', $ui_icon_pack_plugin_manager);

    $element = $base_element;
    $element['#show_settings'] = TRUE;
    $element['#default_value'] = 'bar:baz';
    IconAutocomplete::processIcon($element, $form_state, $complete_form);
    IconAutocomplete::processIconAjaxForm($element, $form_state, $complete_form);
    $this->assertArrayHasKey('#ajax', $element['icon_id']);
    $this->assertSame('baz/qux', $element['icon_id']['#ajax']['options']['query']['element_parents']);

    $this->assertArrayHasKey('icon_settings', $element);
    $this->assertSame('icon[foo_bar]', $element['icon_settings']['#name']);
    $this->assertSame($base_element['#settings_title'], $element['icon_settings']['#title']);

    $this->assertArrayHasKey('sub_form', $element['icon_settings']);
  }

  /**
   * Test the processIconAjaxForm method for #show_settings = FALSE.
   */
  public function testProcessIconAjaxFormNoSettings(): void {
    $form_state = $this->createMock('Drupal\Core\Form\FormState');
    $complete_form = [];

    $icon_id = 'foo:bar';
    $pack_id = 'baz';

    $element = [
      '#parents' => ['foo', 'bar'],
      '#array_parents' => ['baz', 'qux'],
      '#show_settings' => FALSE,
      '#settings_title' => new TranslatableMarkup('Baz'),
      '#value' => [
        'icon_id' => $icon_id,
      ],
    ];

    $icon = $this->createTestIcon([
      'pack_id' => $pack_id,
      'icon_id' => $icon_id,
      'source' => 'foo/path',
      'pack_label' => 'Baz',
    ]);

    $ui_icon_pack_plugin_manager = $this->createMock(IconPackManagerInterface::class);
    $ui_icon_pack_plugin_manager->method('getIcon')
      ->with('foo:bar')
      ->willReturn($icon);
    $this->container->set('plugin.manager.icon_pack', $ui_icon_pack_plugin_manager);

    IconAutocomplete::processIcon($element, $form_state, $complete_form);
    IconAutocomplete::processIconAjaxForm($element, $form_state, $complete_form);
    $this->assertArrayHasKey('#ajax', $element['icon_id']);
  }

  /**
   * Test the processIconAjaxForm #allowed_icon_pack and no extractor form.
   */
  public function testProcessIconAjaxFormAllowedIconPack(): void {
    $form_state = $this->createMock('Drupal\Core\Form\FormState');
    $complete_form = [];

    $icon_id = 'foo:bar';
    $pack_id = 'baz';

    $element = [
      '#parents' => ['foo', 'bar'],
      '#array_parents' => ['baz', 'qux'],
      '#show_settings' => TRUE,
      '#settings_title' => new TranslatableMarkup('Baz'),
      '#value' => [
        'icon_id' => $icon_id,
      ],
    ];

    $icon = $this->createTestIcon([
      'pack_id' => $pack_id,
      'icon_id' => $icon_id,
      'source' => 'foo/path',
      'pack_label' => 'Baz',
    ]);

    $ui_icon_pack_plugin_manager = $this->createMock(IconPackManagerInterface::class);
    $ui_icon_pack_plugin_manager->method('getIcon')
      ->with($icon_id)
      ->willReturn($icon);
    $this->container->set('plugin.manager.icon_pack', $ui_icon_pack_plugin_manager);

    // Test with no Extractor form.
    IconAutocomplete::processIcon($element, $form_state, $complete_form);
    IconAutocomplete::processIconAjaxForm($element, $form_state, $complete_form);
    $this->assertArrayHasKey('#ajax', $element['icon_id']);

    // Test with #allowed_icon_pack value not valid.
    $element['#allowed_icon_pack'] = ['qux'];
    IconAutocomplete::processIcon($element, $form_state, $complete_form);
    IconAutocomplete::processIconAjaxForm($element, $form_state, $complete_form);
    $this->assertArrayHasKey('#ajax', $element['icon_id']);
    $this->assertArrayNotHasKey('icon_settings', $element);
  }

  /**
   * Data provider for ::testValidateIcon().
   *
   * @return array
   *   The data to test.
   */
  public static function providerValidateIcon(): array {
    return [
      'valid icon' => [
        'element' => [
          '#parents' => ['icon'],
          'icon_id' => [
            '#title' => 'Foo',
          ],
        ],
        'pack_id' => 'foo',
        'values' => [
          'icon' => [
            'icon_id' => 'foo:baz',
            'icon_settings' => [
              'foo' => [
                'settings_1' => [],
              ],
            ],
          ],
        ],
        'expected_error' => NULL,
      ],
    ];
  }

  /**
   * Test the validateIcon method.
   *
   * @param array $element
   *   The element data.
   * @param string $pack_id
   *   The icon set id.
   * @param array $values
   *   The values data.
   * @param \Drupal\Core\StringTranslation\TranslatableMarkup|null $expected_error
   *   The expected error message or no message.
   *
   * @dataProvider providerValidateIcon
   */
  public function testValidateIcon(array $element, string $pack_id, array $values, ?TranslatableMarkup $expected_error): void {
    $complete_form = [];
    $settings = $values['icon']['icon_settings'];

    $icon = $this->createTestIcon([
      'icon_id' => explode(IconDefinition::ICON_SEPARATOR, $values['icon']['icon_id'])[1],
      'source' => 'foo/bar',
      'pack_id' => $pack_id,
      'pack_label' => $element['icon_id']['#title'],
    ]);

    $ui_icon_pack_plugin_manager = $this->createMock(IconPackManagerInterface::class);
    $ui_icon_pack_plugin_manager->method('getIcon')
      ->with($icon->getId())
      ->willReturn($icon);
    $this->container->set('plugin.manager.icon_pack', $ui_icon_pack_plugin_manager);

    $form_state = $this->createMock('Drupal\Core\Form\FormState');
    $form_state->method('getValues')
      ->willReturn($values);

    // Main test is to expect the setValueForElement().
    $form_state->expects($this->once())
      ->method('setValueForElement')
      ->with($element, ['icon' => $icon, 'settings' => $settings]);

    IconAutocomplete::validateIcon($element, $form_state, $complete_form);

    // Test #return_id property.
    $element['#return_id'] = TRUE;

    $form_state = $this->createMock('Drupal\Core\Form\FormState');
    $form_state->method('getValues')
      ->willReturn($values);

    // Main test is to expect the setValueForElement() with only target_id.
    $form_state->expects($this->once())
      ->method('setValueForElement')
      ->with($element, ['target_id' => $values['icon']['icon_id'], 'settings' => $settings]);

    IconAutocomplete::validateIcon($element, $form_state, $complete_form);

    // Test $input_exists is FALSE.
    $element['#parents'] = ['foo'];
    IconAutocomplete::validateIcon($element, $form_state, $complete_form);
  }

  /**
   * Test the validateIcon method.
   */
  public function testValidateIconNull(): void {
    $complete_form = [];
    $element = [
      '#parents' => ['icon'],
      '#required' => FALSE,
    ];

    $form_state = $this->createMock(FormStateInterface::class);
    $form_state->method('getValues')
      ->willReturn(['icon' => []]);

    // The test is to expect the setValueForElement().
    $form_state->expects($this->once())
      ->method('setValueForElement')
      ->with($element, NULL);

    IconAutocomplete::validateIcon($element, $form_state, $complete_form);
  }

  /**
   * Test the validateIcon method.
   */
  public function testValidateIconError(): void {
    $complete_form = [];
    $element = [
      '#parents' => ['icon'],
      'icon_id' => [
        '#title' => 'Foo',
      ],
    ];

    $form_state = $this->createMock(FormStateInterface::class);
    $form_state->method('getValues')
      ->willReturn(['icon' => ['icon_id' => 'foo:baz']]);

    // The test is to expect the setError().
    $form_state
      ->expects($this->once())
      ->method('setError')
      ->with($element['icon_id'], new TranslatableMarkup('Icon for %title is invalid: %icon.<br>Please search again and select a result in the list.', [
        '%title' => $element['icon_id']['#title'],
        '%icon' => 'foo:baz',
      ]));

    IconAutocomplete::validateIcon($element, $form_state, $complete_form);

  }

  /**
   * Test the validateIcon method.
   */
  public function testValidateIconErrorNotAllowed(): void {
    $complete_form = [];
    $icon_id = 'bar';
    $pack_id = 'foo';
    $icon_full_id = IconDefinition::createIconId($pack_id, $icon_id);

    $element = [
      '#parents' => ['icon'],
      'icon_id' => [
        '#title' => 'Foo',
      ],
      '#allowed_icon_pack' => ['qux', 'corge'],
    ];

    $icon = $this->createTestIcon([
      'pack_id' => $pack_id,
      'icon_id' => $icon_id,
      'source' => 'foo/path',
      'pack_label' => 'Baz',
    ]);

    $form_state = $this->createMock(FormStateInterface::class);
    $form_state->method('getValues')
      ->willReturn(['icon' => ['icon_id' => $icon_full_id]]);

    $ui_icon_pack_plugin_manager = $this->createMock(IconPackManagerInterface::class);
    $ui_icon_pack_plugin_manager->method('getIcon')
      ->with($icon_full_id)
      ->willReturn($icon);
    $this->container->set('plugin.manager.icon_pack', $ui_icon_pack_plugin_manager);

    // The test is to expect the setError().
    $form_state
      ->expects($this->once())
      ->method('setError')
      ->with($element['icon_id'], new TranslatableMarkup('Icon for %title is not valid anymore because it is part of icon pack: %pack_id. This field limit icon pack to: %limit.', [
        '%title' => $element['icon_id']['#title'],
        '%pack_id' => $pack_id,
        '%limit' => implode(', ', $element['#allowed_icon_pack']),
      ]));

    IconAutocomplete::validateIcon($element, $form_state, $complete_form);
  }

  /**
   * Test the validateIcon method.
   */
  public function testValidateIconEmpty(): void {
    $form_state = $this->createMock(FormStateInterface::class);
    $complete_form = [];
    $element = ['#parents' => ['foo']];
    IconAutocomplete::validateIcon($element, $form_state, $complete_form);
    $this->assertEquals(['#parents' => ['foo']], $element);
  }

  /**
   * Test the valueCallback method.
   */
  public function testValueCallback(): void {
    $element = [];

    $icon_id = 'bar';
    $pack_id = 'foo';
    $icon_full_id = IconDefinition::createIconId($pack_id, $icon_id);

    $input = [
      'icon_id' => $icon_full_id,
      'icon_settings' => ['foo' => 'bar'],
    ];

    $icon = $this->createTestIcon([
      'pack_id' => $pack_id,
      'icon_id' => $icon_id,
      'source' => 'foo/path',
      'pack_label' => 'Baz',
    ]);

    $form_state = $this->createMock(FormStateInterface::class);

    $ui_icon_pack_plugin_manager = $this->createMock(IconPackManagerInterface::class);
    $ui_icon_pack_plugin_manager->method('getIcon')
      ->with($icon_full_id)
      ->willReturn($icon);
    $this->container->set('plugin.manager.icon_pack', $ui_icon_pack_plugin_manager);

    $actual = IconAutocomplete::valueCallback($element, $input, $form_state);

    $expected = [
      'icon_id' => $icon_full_id,
      'icon_settings' => $input['icon_settings'],
      'object' => $icon,
    ];
    $this->assertSame($expected, $actual);

    // Test default_value with no icon_id.
    $input = FALSE;
    $element['#default_value'] = $icon_full_id;

    $actual = IconAutocomplete::valueCallback($element, $input, $form_state);

    $expected = [
      'object' => $icon,
    ];
    $this->assertSame($expected, $actual);

    // Test empty icon_id.
    $input = ['icon_id' => ''];
    $element['#default_value'] = 'foo';

    $actual = IconAutocomplete::valueCallback($element, $input, $form_state);
    $this->assertSame([], $actual);
  }

  /**
   * Test the valueCallback method with no Icon.
   */
  public function testValueCallbackInvalidIcon(): void {
    $element = [];

    $input = [
      'icon_id' => 'foo:bar',
      'icon_settings' => ['foo' => 'bar'],
    ];

    $form_state = $this->createMock(FormStateInterface::class);

    $ui_icon_pack_plugin_manager = $this->createMock(IconPackManagerInterface::class);
    $ui_icon_pack_plugin_manager->method('getIcon')
      ->willReturn(NULL);
    $this->container->set('plugin.manager.icon_pack', $ui_icon_pack_plugin_manager);

    $actual = IconAutocomplete::valueCallback($element, $input, $form_state);
    $this->assertSame($input, $actual);
  }

  /**
   * Test the valueCallback method with no callback.
   */
  public function testValueCallbackNoCallback(): void {
    $element = [];

    $input = FALSE;

    $form_state = $this->createMock(FormStateInterface::class);

    $ui_icon_pack_plugin_manager = $this->createMock(IconPackManagerInterface::class);
    $ui_icon_pack_plugin_manager->method('getIcon')
      ->willReturn(NULL);
    $this->container->set('plugin.manager.icon_pack', $ui_icon_pack_plugin_manager);

    $actual = IconAutocomplete::valueCallback($element, $input, $form_state);
    $this->assertSame($input, $actual);
  }

  /**
   * Test the buildAjaxCallback method.
   */
  public function testBuildAjaxCallback(): void {
    $form = [
      'foo' => [
        '#prefix' => '',
        '#attached' => ['foo/bar'],
      ],
    ];
    $form_state = $this->createMock(FormStateInterface::class);

    $request = new Request(['element_parents' => 'foo']);

    $prophecy = $this->prophesize(RendererInterface::class);
    $prophecy->renderRoot(Argument::any())->willReturn('_rendered_');
    $renderer = $prophecy->reveal();
    $this->container->set('renderer', $renderer);

    $actual = IconAutocomplete::buildAjaxCallback($form, $form_state, $request);

    $expected = [
      'command' => 'insert',
      'method' => 'replaceWith',
      'selector' => NULL,
      'data' => '_rendered_',
      'settings' => NULL,
    ];
    $this->assertSame([$expected], $actual->getCommands());
  }

  /**
   * Create a mock icon.
   *
   * @param array<string, string>|null $iconData
   *   The icon data to create.
   *
   * @return \Drupal\Core\Theme\Icon\IconDefinitionInterface
   *   The icon mocked.
   */
  private function createMockIcon(?array $iconData = NULL): IconDefinitionInterface {
    if (NULL === $iconData) {
      $iconData = [
        'pack_id' => 'foo',
        'icon_id' => 'bar',
      ];
    }

    $icon = $this->prophesize(IconDefinitionInterface::class);
    $icon
      ->getRenderable(['width' => $iconData['width'] ?? '', 'height' => $iconData['height'] ?? ''])
      ->willReturn(['#markup' => '<svg></svg>']);

    $icon_full_id = IconDefinition::createIconId($iconData['pack_id'], $iconData['icon_id']);
    $icon
      ->getId()
      ->willReturn($icon_full_id);

    return $icon->reveal();
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc