Commit b95f4d61 authored by Bojan Živanović's avatar Bojan Živanović
Browse files

Revert "Issue #2995992 by jcandan, dww, edsoncarlos: Country field should...

Revert "Issue #2995992 by jcandan, dww, edsoncarlos: Country field should display the "- Select -" option to match core behavior"

This reverts commit 502ee52c.
parent 84455dd2
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -118,7 +118,9 @@ class Address extends FormElement {
    // Preselect the default country to ensure it's present in the value.
    $element['#default_value'] = (array) $element['#default_value'];
    $element['#default_value'] = self::applyDefaults($element['#default_value']);

    if (empty($element['#default_value']['country_code']) && $element['#required']) {
      $element['#default_value']['country_code'] = Country::getDefaultCountry($element['#available_countries']);
    }
    // Any input with a NULL or missing country_code is considered invalid.
    // Even if the element is optional and no country is selected, the
    // country_code would be an empty string, not NULL.
+10 −5
Original line number Diff line number Diff line
@@ -73,14 +73,18 @@ class Country extends FormElement {
      $available_countries = array_combine($available_countries, $available_countries);
      $country_list = array_intersect_key($country_list, $available_countries);
    }
    if (empty($element['#default_value']) && $element['#required']) {
      // Fallback to the first country in the list if the default country
      // is empty even though the field is required.
      $element['#default_value'] = key($country_list);
    }

    $element['#tree'] = TRUE;
    // Hide the dropdown when there is only one possible value.
    // Filter out empty select options before count.
    if (count(array_filter($country_list)) == 1 && $element['#required']) {
    if (count($country_list) == 1 && $element['#required']) {
      $element['country_code'] = [
        '#type' => 'hidden',
        '#value' => key(array_filter($available_countries)),
        '#value' => key($available_countries),
      ];
    }
    else {
@@ -91,8 +95,6 @@ class Country extends FormElement {
        '#description_display' => $element['#description_display'],
        '#options' => $country_list,
        '#default_value' => $element['#default_value'],
        '#empty_value' => '',
        '#empty_option' => t('- Select -'),
        '#required' => $element['#required'],
        '#limit_validation_errors' => [],
        '#attributes' => [
@@ -101,6 +103,9 @@ class Country extends FormElement {
        ],
        '#weight' => -100,
      ];
      if (!$element['#required']) {
        $element['country_code']['#empty_value'] = '';
      }
      if (!empty($element['#ajax'])) {
        $element['country_code']['#ajax'] = $element['#ajax'];
        unset($element['#ajax']);
+0 −159
Original line number Diff line number Diff line
<?php

namespace Drupal\Tests\address\FunctionalJavascript;

use Drupal\field\Entity\FieldConfig;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;

/**
 * Tests the default address widget.
 *
 * @group address
 */
class AddressCountryDefaultWidgetTest extends WebDriverTestBase {

  use AddressWidgetTestTrait;

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    'system',
    'language',
    'user',
    'field',
    'field_ui',
    'node',
    'address',
  ];

  /**
   * {@inheritdoc}
   */
  protected $defaultTheme = 'stark';

  /**
   * {@inheritdoc}
   */
  protected function setUp() {
    parent::setUp();
    $this->initialize('field_country', 'address_country', 'Country', 'value', 'address_country_default');
  }

  /**
   * Tests the country field.
   *
   * Checked:
   * - required/optional status.
   * - available_countries instance setting.
   */
  public function testCountries() {
    $field_name = $this->field->getName() . '[0][value]';
    // Optional field: Country should be optional and set to default_country.
    $this->drupalGet($this->nodeAddUrl);
    $this->assertEmpty((bool) $this->xpath('//select[@name="' . $field_name . '" and boolean(@required)]'), 'Country is shown as optional.');
    $this->assertOptionSelected($field_name, 'US', 'The configured default_country is selected.');

    // Required field: Country should be required and set to default_country.
    $this->field->setRequired(TRUE);
    $this->field->save();
    $this->drupalGet($this->nodeAddUrl);
    $this->assertNotEmpty((bool) $this->xpath('//select[@name="' . $field_name . '" and boolean(@required)]'), 'Country is shown as required.');
    $this->assertOptionSelected($field_name, 'US', 'The configured default_country is selected.');

    // Required field with no default.
    // Country should be required and set to empty value option.
    $this->field->setDefaultValue([['value' => '']]);
    $this->field->save();
    $this->drupalGet($this->nodeAddUrl);
    $this->assertNotEmpty((bool) $this->xpath('//select[@name="' . $field_name . '" and boolean(@required)]'), 'Country is shown as required.');
    $this->assertOptionSelected($field_name, '', 'The empty option is selected.');

    // All countries should be present in the form.
    $countries = array_keys($this->countryRepository->getList());
    $this->assertOptions($field_name, $countries, 'All countries are present.');

    // Limit the list of available countries.
    $countries = ['US', 'FR', 'BR', 'JP'];
    $edit = [];
    $edit['settings[available_countries][]'] = array_map(function ($country) {
      return $country;
    }, $countries);
    $this->drupalGet($this->fieldConfigUrl);
    $this->submitForm($edit, t('Save settings'));
    $this->drupalGet($this->nodeAddUrl);
    $this->assertOptions($field_name, $countries, 'The restricted list of available countries is present.');

    // Create an article with one of them.
    $country_code = 'US';
    $this->getSession()->getPage()->fillField($field_name, 'US');
    $this->assertSession()->assertWaitOnAjaxRequest();

    $edit = [];
    $edit['title[0][value]'] = $this->randomMachineName(8);
    $this->submitForm($edit, t('Save'));
    // Check that the article has been created.
    $node = $this->getNodeByTitle($edit['title[0][value]']);
    $this->assertNotEmpty($node, 'Created article ' . $edit['title[0][value]']);

    // Now remove 'US' from the list of available countries.
    $countries = ['FR'];
    $edit = [];
    $edit['settings[available_countries][]'] = array_map(function ($country) {
      return $country;
    }, $countries);
    $this->drupalPostForm($this->fieldConfigUrl, $edit, t('Save settings'));

    // Access the article's edit form and confirm the values are unchanged.
    // 'US' should be in the list along with the available countries and should
    // be selected.
    $this->drupalGet('node/' . $node->id() . '/edit');
    $this->assertOptionSelected($field_name, $country_code);
    // Confirm that it is possible to switch the country to France, and back.
    $this->getSession()->getPage()->fillField($field_name, 'FR');
    $this->assertSession()->assertWaitOnAjaxRequest();
    $this->getSession()->getPage()->fillField($field_name, 'US');
    $this->assertSession()->assertWaitOnAjaxRequest();

    // Test the widget with only one available country.
    // Since the field is required, the country selector should be hidden.
    $countries = ['US'];
    $edit = [];
    $edit['settings[available_countries][]'] = array_map(function ($country) {
      return $country;
    }, $countries);
    $this->drupalPostForm($this->fieldConfigUrl, $edit, t('Save settings'));

    $this->drupalGet('node/' . $node->id() . '/edit');
    $this->assertSession()->fieldNotExists($field_name . '[0][value]');
  }

  /**
   * Tests the default value functionality.
   */
  public function testDefaultValue() {
    $this->drupalGet($this->fieldConfigUrl);
    // Confirm that the US is selected by default.
    $this->assertSession()->fieldValueEquals('default_value_input[field_country][0][value]', 'US');
    // Confirm that it is possible to switch the country to France.
    $this->getSession()->getPage()->fillField('default_value_input[field_country][0][value]', 'FR');
    $this->assertSession()->assertWaitOnAjaxRequest();
    $edit = [];
    $this->submitForm($edit, t('Save settings'));
    $this->assertSession()->pageTextContains('Saved Country configuration.');

    $this->container->get('entity_type.manager')->getStorage('field_config')->resetCache();
    $this->field = FieldConfig::load($this->field->id());
    $default_value = $this->field->getDefaultValueLiteral();
    $expected_default_value = [
      'value' => 'FR',
    ];
    $this->assertCount(1, $default_value);
    $this->assertEquals($expected_default_value, array_filter($default_value[0]));

    // Confirm that the default value is used on the node form.
    $this->drupalGet($this->nodeAddUrl);
    $this->assertSession()->fieldValueEquals('field_country[0][value]', 'FR');
  }

}
+184 −22
Original line number Diff line number Diff line
@@ -2,8 +2,12 @@

namespace Drupal\Tests\address\FunctionalJavascript;

use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\node\Entity\NodeType;

/**
 * Tests the default address widget.
@@ -12,12 +16,12 @@ use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
 */
class AddressDefaultWidgetTest extends WebDriverTestBase {

  use AddressWidgetTestTrait;

  /**
   * {@inheritdoc}
   * Modules to enable.
   *
   * @var array
   */
  protected static $modules = [
  public static $modules = [
    'system',
    'language',
    'user',
@@ -32,6 +36,48 @@ class AddressDefaultWidgetTest extends WebDriverTestBase {
   */
  protected $defaultTheme = 'stark';

  /**
   * User with permission to administer entities.
   *
   * @var \Drupal\user\UserInterface
   */
  protected $adminUser;

  /**
   * Address field instance.
   *
   * @var \Drupal\field\FieldConfigInterface
   */
  protected $field;

  /**
   * Entity form display.
   *
   * @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface
   */
  protected $formDisplay;

  /**
   * URL to add new content.
   *
   * @var string
   */
  protected $nodeAddUrl;

  /**
   * URL to field's configuration form.
   *
   * @var string
   */
  protected $fieldConfigUrl;

  /**
   * The country repository.
   *
   * @var \CommerceGuys\Addressing\Country\CountryRepositoryInterface
   */
  protected $countryRepository;

  /**
   * The subdivision repository.
   *
@@ -51,7 +97,60 @@ class AddressDefaultWidgetTest extends WebDriverTestBase {
   */
  protected function setUp() {
    parent::setUp();
    $this->initialize('field_address', 'address', 'Address', 'country_code', 'address_default');

    // Create node bundle for tests.
    $type = NodeType::create(['name' => 'Article', 'type' => 'article']);
    $type->save();

    // Create user that will be used for tests.
    $this->adminUser = $this->createUser([
      'create article content',
      'edit own article content',
      'administer content types',
      'administer node fields',
    ]);
    $this->drupalLogin($this->adminUser);

    // Add the address field to the article content type.
    $field_storage = FieldStorageConfig::create([
      'field_name' => 'field_address',
      'entity_type' => 'node',
      'type' => 'address',
    ]);
    $field_storage->save();

    $this->field = FieldConfig::create([
      'field_storage' => $field_storage,
      'bundle' => 'article',
      'label' => 'Address',
      'default_value' => [
        [
          'country_code' => 'US',
        ],
      ],
    ]);
    $this->field->save();

    // Set article's form display.
    $this->formDisplay = EntityFormDisplay::load('node.article.default');

    if (!$this->formDisplay) {
      EntityFormDisplay::create([
        'targetEntityType' => 'node',
        'bundle' => 'article',
        'mode' => 'default',
        'status' => TRUE,
      ])->save();
      $this->formDisplay = EntityFormDisplay::load('node.article.default');
    }
    $this->formDisplay->setComponent($this->field->getName(), [
      'type' => 'address_default',
    ])->save();

    $this->nodeAddUrl = 'node/add/article';
    $this->fieldConfigUrl = 'admin/structure/types/manage/article/fields/node.article.' . $this->field->getName();

    $this->countryRepository = \Drupal::service('address.country_repository');
    $this->subdivisionRepository = \Drupal::service('address.subdivision_repository');
    $this->addressFormatRepository = \Drupal::service('address.address_format_repository');
  }
@@ -166,18 +265,6 @@ class AddressDefaultWidgetTest extends WebDriverTestBase {
    $this->assertSession()->fieldValueEquals($field_name . '[0][address][locality]', $address['locality']);
    $this->assertOptionSelected($field_name . '[0][address][administrative_area]', $address['administrative_area']);
    $this->assertSession()->fieldValueEquals($field_name . '[0][address][postal_code]', $address['postal_code']);

    // Required field with no default.
    // Country should be required and set to empty value option.
    $this->field->setDefaultValue([]);
    $this->field->setRequired(TRUE);
    $this->field->save();
    $this->drupalGet($this->nodeAddUrl);
    $this->assertNotEmpty((bool) $this->xpath('//select[@name="' . $field_name . '[0][address][country_code]" and boolean(@required)]'), 'Country is shown as required.');
    $this->assertOptionSelected($field_name . '[0][address][country_code]', '', 'The empty option is selected.');
    // All countries should be present in the form.
    $countries = array_keys($this->countryRepository->getList());
    $this->assertOptions($field_name . '[0][address][country_code]', $countries, 'All countries are present.');
  }

  /**
@@ -344,11 +431,7 @@ class AddressDefaultWidgetTest extends WebDriverTestBase {
    $locality = 'Chengde Shi';
    $administrative_areas = $this->subdivisionRepository->getList([$country]);
    $localities = $this->subdivisionRepository->getList([$country, $administrative_area]);
    $dependent_localities = $this->subdivisionRepository->getList([
      $country,
      $administrative_area,
      $locality,
    ]);
    $dependent_localities = $this->subdivisionRepository->getList([$country, $administrative_area, $locality]);
    // Confirm the presence and format of the administrative area dropdown.
    $this->drupalGet($this->nodeAddUrl);
    $this->getSession()->getPage()->fillField($field_name . '[0][address][country_code]', $country);
@@ -412,4 +495,83 @@ class AddressDefaultWidgetTest extends WebDriverTestBase {
    $this->assertSession()->fieldValueEquals($field_name . '[0][address][postal_code]', '');
  }

  /**
   * Asserts that a select field has all of the provided options.
   *
   * Core only has assertOption(), this helper decreases the number of needed
   * assertions.
   *
   * @param string $id
   *   ID of select field to assert.
   * @param array $options
   *   Options to assert.
   * @param string $message
   *   (optional) A message to display with the assertion. Do not translate
   *   messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
   *   variables in the message text, not t(). If left blank, a default message
   *   will be displayed.
   */
  protected function assertOptions($id, array $options, $message = '') {
    $elements = $this->xpath('//select[@name="' . $id . '"]/option');
    $found_options = [];
    foreach ($elements as $element) {
      if ($option = $element->getValue()) {
        $found_options[] = $option;
      }
    }
    $this->assertFieldValues($found_options, $options, $message);
  }

  /**
   * Asserts that a select field has a selected option.
   *
   * @param string $id
   *   ID of select field to assert.
   * @param string $option
   *   Option to assert.
   * @param string $message
   *   (optional) A message to display with the assertion. Do not translate
   *   messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
   *   variables in the message text, not t(). If left blank, a default message
   *   will be displayed.
   */
  protected function assertOptionSelected($id, $option, $message = '') {
    $elements = $this->xpath('//select[@name=:id]//option[@value=:option]', [':id' => $id, ':option' => $option]);
    foreach ($elements as $element) {
      $this->assertNotEmpty($element->isSelected(), $message ? $message : new FormattableMarkup('Option @option for field @id is selected.', ['@option' => $option, '@id' => $id]));
    }
  }

  /**
   * Asserts that the passed field values are correct.
   *
   * Ignores differences in ordering.
   *
   * @param array $field_values
   *   The field values.
   * @param array $expected_values
   *   The expected values.
   * @param string $message
   *   (optional) A message to display with the assertion. Do not translate
   *   messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
   *   variables in the message text, not t(). If left blank, a default message
   *   will be displayed.
   */
  protected function assertFieldValues(array $field_values, array $expected_values, $message = '') {
    $valid = TRUE;
    if (count($field_values) == count($expected_values)) {
      foreach ($expected_values as $value) {
        if (!in_array($value, $field_values)) {
          $valid = FALSE;
          break;
        }
      }
    }
    else {
      $valid = FALSE;
    }

    $this->assertTrue($valid, $message);
  }

}
+0 −228
Original line number Diff line number Diff line
<?php

namespace Drupal\Tests\address\FunctionalJavascript;

use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\node\Entity\NodeType;

/**
 * Provides common properties and methods to Address widget tests.
 */
trait AddressWidgetTestTrait {

  /**
   * User with permission to administer entities.
   *
   * @var \Drupal\user\UserInterface
   */
  protected $adminUser;

  /**
   * Address field instance.
   *
   * @var \Drupal\field\FieldConfigInterface
   */
  protected $field;

  /**
   * Entity form display.
   *
   * @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface
   */
  protected $formDisplay;

  /**
   * URL to add new content.
   *
   * @var string
   */
  protected $nodeAddUrl;

  /**
   * URL to field's configuration form.
   *
   * @var string
   */
  protected $fieldConfigUrl;

  /**
   * The country repository.
   *
   * @var \CommerceGuys\Addressing\Country\CountryRepositoryInterface
   */
  protected $countryRepository;

  /**
   * Initializes Address widget tests.
   *
   * - Creates a node bundle and requested field configuration.
   * - Creates an admin user and logs them in.
   * - Initializes protected data members.
   *
   * @param string $field_name
   *   The field storage configured field name.
   * @param string $field_type
   *   The field storage configured type.
   * @param string $label
   *   The field configured label.
   * @param string $default_value_key
   *   The default value key.
   * @param string $form_display_widget_type
   *   The form display widget type.
   *
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  protected function initialize($field_name, $field_type, $label, $default_value_key, $form_display_widget_type) {
    // Create node bundle for tests.
    $type = NodeType::create(['name' => 'Article', 'type' => 'article']);
    $type->save();
    $this->nodeAddUrl = 'node/add/article';

    // Create user that will be used for tests.
    $this->adminUser = $this->createUser([
      'create article content',
      'edit own article content',
      'administer content types',
      'administer node fields',
    ]);
    $this->drupalLogin($this->adminUser);

    // Add the address field to the article content type.
    $field_storage = FieldStorageConfig::create([
      'field_name' => $field_name,
      'entity_type' => 'node',
      'type' => $field_type,
    ]);
    $field_storage->save();

    $this->field = FieldConfig::create([
      'field_storage' => $field_storage,
      'bundle' => 'article',
      'label' => $label,
      'default_value' => [
        [
          $default_value_key => 'US',
        ],
      ],
    ]);
    $this->field->save();

    // Set article's form display.
    $this->formDisplay = EntityFormDisplay::load('node.article.default');

    if (!$this->formDisplay) {
      EntityFormDisplay::create([
        'targetEntityType' => 'node',
        'bundle' => 'article',
        'mode' => 'default',
        'status' => TRUE,
      ])->save();
      $this->formDisplay = EntityFormDisplay::load('node.article.default');
    }
    $this->formDisplay->setComponent($this->field->getName(), [
      'type' => $form_display_widget_type,
    ])->save();

    $this->fieldConfigUrl = 'admin/structure/types/manage/article/fields/node.article.' . $this->field->getName();

    $this->countryRepository = \Drupal::service('address.country_repository');
  }

  /**
   * Asserts that a select field has all of the provided options.
   *
   * Core only has assertOption(), this helper decreases the number of needed
   * assertions.
   *
   * @param string $id
   *   ID of select field to assert.
   * @param array $options
   *   Options to assert.
   * @param string $message
   *   (optional) A message to display with the assertion. Do not translate
   *   messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
   *   variables in the message text, not t(). If left blank, a default message
   *   will be displayed.
   */
  protected function assertOptions($id, array $options, $message = '') {
    $elements = $this->xpath('//select[@name="' . $id . '"]/option');
    // Assume an empty option is valid.
    // Look through the options array_keys for blank keys. This is the empty
    // "- Select -" option. Because a blank value would not be listed as a
    // valid field value, filter out the empty option before we check this list
    // against valid field values.
    $elements = array_filter($elements, function ($k) {
      return !empty($k);
    }, ARRAY_FILTER_USE_KEY);
    $found_options = [];
    foreach ($elements as $element) {
      if ($option = $element->getValue()) {
        $found_options[] = $option;
      }
    }
    $this->assertFieldValues($found_options, $options, $message);
  }

  /**
   * Asserts that a select field has a selected option.
   *
   * @param string $id
   *   ID of select field to assert.
   * @param string $option
   *   Option to assert.
   * @param string $message
   *   (optional) A message to display with the assertion. Do not translate
   *   messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
   *   variables in the message text, not t(). If left blank, a default message
   *   will be displayed.
   */
  protected function assertOptionSelected($id, $option, $message = '') {
    if ($option === '') {
      $elements = $this->xpath('//select[@name=:id]//option[not(@value) or (@value and string-length(@value)=0)]', [':id' => $id]);
      $this->assertNotEmpty($elements, new FormattableMarkup('A blank option for field @id is not found.', ['@id' => $id]));
    }
    else {
      $elements = $this->xpath('//select[@name=:id]//option[@value=:option]', [':id' => $id, ':option' => $option]);
      $this->assertNotEmpty($elements, new FormattableMarkup('Option @option for field @id is not found.', ['@option' => $option, '@id' => $id]));
    }
    foreach ($elements as $element) {
      $this->assertNotEmpty($element->isSelected(), $message ? $message : new FormattableMarkup('Option @option for field @id is selected.', ['@option' => $option, '@id' => $id]));
    }
  }

  /**
   * Asserts that the passed field values are correct.
   *
   * Ignores differences in ordering.
   *
   * @param array $field_values
   *   The field values.
   * @param array $expected_values
   *   The expected values.
   * @param string $message
   *   (optional) A message to display with the assertion. Do not translate
   *   messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
   *   variables in the message text, not t(). If left blank, a default message
   *   will be displayed.
   */
  protected function assertFieldValues(array $field_values, array $expected_values, $message = '') {
    $valid = TRUE;
    if (count($field_values) == count($expected_values)) {
      foreach ($expected_values as $value) {
        if (!in_array($value, $field_values)) {
          $valid = FALSE;
          break;
        }
      }
    }
    else {
      $valid = FALSE;
    }

    $this->assertTrue($valid, $message);
  }

}