diff --git a/core/modules/action/tests/action_form_ajax_test/action_form_ajax_test.info.yml b/core/modules/action/tests/action_form_ajax_test/action_form_ajax_test.info.yml index 020e9ef45d..8daf9812f0 100644 --- a/core/modules/action/tests/action_form_ajax_test/action_form_ajax_test.info.yml +++ b/core/modules/action/tests/action_form_ajax_test/action_form_ajax_test.info.yml @@ -1,7 +1,7 @@ name: action_form_ajax_test type: module description: 'module used for testing ajax in action config entity forms.' -package: Core +package: Testing version: VERSION core: 8.x hidden: true diff --git a/core/modules/content_moderation/src/EntityTypeInfo.php b/core/modules/content_moderation/src/EntityTypeInfo.php index 30a0a51ca4..8d86e8a485 100644 --- a/core/modules/content_moderation/src/EntityTypeInfo.php +++ b/core/modules/content_moderation/src/EntityTypeInfo.php @@ -3,6 +3,7 @@ namespace Drupal\content_moderation; use Drupal\content_moderation\Plugin\Field\ModerationStateFieldItemList; +use Drupal\content_moderation\Plugin\Field\ModerationStateItem; use Drupal\Core\Entity\BundleEntityFormBase; use Drupal\Core\Entity\ContentEntityFormInterface; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; @@ -265,6 +266,7 @@ public function entityBaseFieldInfo(EntityTypeInterface $entity_type) { ->setDisplayConfigurable('view', FALSE) ->setReadOnly(FALSE) ->setTranslatable(TRUE); + $fields['moderation_state']->getItemDefinition()->setClass(ModerationStateItem::class); return $fields; } diff --git a/core/modules/content_moderation/src/Plugin/Field/ModerationStateItem.php b/core/modules/content_moderation/src/Plugin/Field/ModerationStateItem.php new file mode 100644 index 0000000000..1d63308d56 --- /dev/null +++ b/core/modules/content_moderation/src/Plugin/Field/ModerationStateItem.php @@ -0,0 +1,22 @@ + $definition) { if ($definition->isComputed() || (!empty($storage_definitions[$field_name]) && _content_translation_is_field_translatability_configurable($entity_type, $storage_definitions[$field_name]))) { $form['settings'][$entity_type_id][$bundle]['fields'][$field_name] = [ + '#title' => $definition->getLabel(), + // @todo Remove this? '#label' => $definition->getLabel(), '#type' => 'checkbox', '#default_value' => $definition->isTranslatable(), @@ -154,6 +156,8 @@ function _content_translation_form_language_content_settings_form_alter(array &$ // entity might have fields and if there are fields to translate. $form['settings'][$entity_type_id][$bundle]['translatable'] = [ '#type' => 'checkbox', + '#title' => t('Translatable'), + '#title_display' => 'invisible', '#default_value' => $content_translation_manager->isEnabled($entity_type_id, $bundle), ]; } diff --git a/core/modules/language/src/Form/ContentLanguageSettingsForm.php b/core/modules/language/src/Form/ContentLanguageSettingsForm.php index 6019917f92..7cebca9785 100644 --- a/core/modules/language/src/Form/ContentLanguageSettingsForm.php +++ b/core/modules/language/src/Form/ContentLanguageSettingsForm.php @@ -118,6 +118,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { foreach ($bundles[$entity_type_id] as $bundle => $bundle_info) { $form['settings'][$entity_type_id][$bundle]['settings'] = [ '#type' => 'item', + // @todo Remove this? '#label' => $bundle_info['label'], '#title' => $bundle_info['label'], '#title_display' => 'invisible', diff --git a/core/modules/link/tests/modules/link_test_base_field/link_test_base_field.info.yml b/core/modules/link/tests/modules/link_test_base_field/link_test_base_field.info.yml index ce07b8f95f..407936124c 100644 --- a/core/modules/link/tests/modules/link_test_base_field/link_test_base_field.info.yml +++ b/core/modules/link/tests/modules/link_test_base_field/link_test_base_field.info.yml @@ -1,5 +1,6 @@ name: Link test base field description: Tests link field as an optional base field +package: Testing type: module core: 8.x hidden: true diff --git a/core/modules/menu_link_content/tests/menu_link_content_dynamic_route/menu_link_content_dynamic_route.info.yml b/core/modules/menu_link_content/tests/menu_link_content_dynamic_route/menu_link_content_dynamic_route.info.yml index 8de1db409a..44067295d0 100644 --- a/core/modules/menu_link_content/tests/menu_link_content_dynamic_route/menu_link_content_dynamic_route.info.yml +++ b/core/modules/menu_link_content/tests/menu_link_content_dynamic_route/menu_link_content_dynamic_route.info.yml @@ -1,6 +1,7 @@ name: 'Menu link content dynamic route' type: module core: 8.x +package: Testing hidden: true dependencies: - drupal:menu_link_content diff --git a/core/modules/menu_link_content/tests/outbound_processing_test/outbound_processing_test.info.yml b/core/modules/menu_link_content/tests/outbound_processing_test/outbound_processing_test.info.yml index 6f4e4a9b39..14982c7b7b 100644 --- a/core/modules/menu_link_content/tests/outbound_processing_test/outbound_processing_test.info.yml +++ b/core/modules/menu_link_content/tests/outbound_processing_test/outbound_processing_test.info.yml @@ -1,4 +1,5 @@ name: 'Outbound route/path processing' type: module core: 8.x +package: Testing hidden: true diff --git a/core/modules/options/src/Plugin/migrate/field/d6/OptionWidgetsField.php b/core/modules/options/src/Plugin/migrate/field/d6/OptionWidgetsField.php index b6b5516aa0..65de5e32ac 100644 --- a/core/modules/options/src/Plugin/migrate/field/d6/OptionWidgetsField.php +++ b/core/modules/options/src/Plugin/migrate/field/d6/OptionWidgetsField.php @@ -1,6 +1,6 @@ save(); $this->container->get('current_user')->setAccount($user); - // Install all non-test modules. - $all_modules = array_filter(system_rebuild_module_data(), function ($module) { - return $module->info['package'] !== 'Testing'; - }); - $this->container->get('module_installer')->install(array_keys($all_modules)); + // Filter out contrib, hidden, and testing modules. We also don't need to + // enable modules that are already enabled. + $all_modules = array_keys(array_filter(system_rebuild_module_data(), function ($module) { + return $module->origin === 'core' && + empty($module->info['hidden']) && + $module->status == FALSE && + $module->info['package'] !== 'Testing'; + })); + $this->container->get('module_installer')->install($all_modules); + // Needed by Drupal\update\Form\UpdateManagerInstall. + if (!defined('DRUPAL_TEST_IN_CHILD_SITE')) { + define('DRUPAL_TEST_IN_CHILD_SITE', FALSE); + } } /** @@ -96,6 +130,8 @@ public function alter(ContainerBuilder $container) { /** * Performs accessibility tests on all forms provided by modules. + * + * @group legacy */ public function testFormAccessibility() { // Find all forms. @@ -104,9 +140,6 @@ public function testFormAccessibility() { // Create an entity of each type needed by forms. $entities = $this->createEntities(); - // Prepare the additional parameters for special form classes. - $this->prepareForms($entities); - // Loop over each entity. foreach ($entities as $entity) { $this->checkEntityListForm($entity, $entities); @@ -183,7 +216,7 @@ protected function findForms() { } } - return $forms; + return array_diff($forms, $this->formsToSkip); } /** @@ -287,6 +320,16 @@ protected function getEntityValues(array $entity_types) { 'targetEntityType' => 'user', 'bundle' => 'user', 'mode' => 'default', + 'third_party_settings' => [ + 'layout_builder' => [ + 'enabled' => TRUE, + 'sections' => [ + new Section('layout_test_plugin', [], [ + 'first-uuid' => new SectionComponent('first-uuid', 'main', ['id' => 'foo']), + ]), + ], + ], + ], ]; $entity_values['image_style'] = [ 'effects' => [ @@ -311,22 +354,45 @@ protected function getEntityValues(array $entity_types) { $entity_values['menu_link_content'] = [ 'menu_name' => 'mock', ]; + $entity_values['node_type'] = [ + 'type' => 'my_book', + ]; + $entity_values['workflow'] = [ + 'type' => 'content_moderation', + 'type_settings' => [ + 'entity_types' => [ + 'node' => [ + 'my_book', + ], + ], + ], + ]; $entity_values['node'] = [ 'uid' => 1, 'status' => TRUE, 'published' => TRUE, + 'content_translation_source' => 'en', 'type' => 'my_book', 'title' => 'My book', 'book' => [ 'bid' => 'new', ], ]; - $entity_values['node_type'] = [ - 'type' => 'my_book', - ]; $entity_values['search_page'] = [ 'plugin' => 'user_search', ]; + $entity_values['workspace'] = [ + 'id' => 'my_workspace', + ]; + + $entity_values = array_intersect_key($entity_values, $entity_types); + // Add a langcode to each entity that needs one. + foreach ($entity_values as $entity_type_id => $entity_value) { + $langcode_key = $entity_types[$entity_type_id]->getKey('langcode'); + if ($langcode_key && !isset($entity_value[$langcode_key])) { + $entity_values[$entity_type_id][$langcode_key] = 'en'; + } + } return $entity_values; } @@ -345,9 +411,9 @@ protected function createEntities() { // Load existing entities for use by forms. $entities = []; - $entities['configurable_language'] = ConfigurableLanguage::load('en'); - $entities['media_type'] = MediaType::load('file'); - $entities['workflow'] = Workflow::load('editorial'); + $entities['configurable_language'] = ConfigurableLanguage::createFromLangcode('es'); + $entities['configurable_language']->save(); + $entities['media_type'] = $this->createMediaType('file'); // Remove any entity types with existing entities from the list to process. $entity_values = array_diff_key($entity_values, $entities); @@ -384,71 +450,18 @@ protected function createEntities() { $entities[$entity_type_id] = $entity; } + + // Add a translation of a node. + $this->container->get('content_translation.manager')->setEnabled('node', $entities['node']->bundle(), TRUE); + $entities['node'] = Node::load($entities['node']->id()); + $entities['node']->addTranslation('es', [ + 'title' => 'Translated node title', + ]); + $entities['node']->save(); + return $entities; } - /** - * Performs any operations needed to process forms. - * - * @param \Drupal\Core\Entity\EntityInterface[] $entities - * An array of entities keyed by entity type ID. - */ - protected function prepareForms(array $entities) { - // Needed for \Drupal\ban\Form\BanDelete. - $this->container->get('ban.ip_manager')->banIp('86.7.5.309'); - - // Needed for Drupal\system\Form\CronForm. - $this->container->get('state')->set('system.cron_key', Crypt::randomBytesBase64(55)); - - // Needed for \Drupal\update\Form\UpdateManagerInstall. - if (!defined('DRUPAL_TEST_IN_CHILD_SITE')) { - define('DRUPAL_TEST_IN_CHILD_SITE', FALSE); - } - - // Needed for \Drupal\Core\Installer\Form\SelectLanguageForm. - require_once $this->root . '/core/includes/install.core.inc'; - - $tempstore = $this->container->get('user.private_tempstore'); - - // Needed for \Drupal\user\Form\UserMultipleCancelConfirm. - $tempstore->get('user_user_operations_cancel')->set(1, [User::load(2)]); - - // Needed for \Drupal\node\Form\DeleteMultiple. - $tempstore->get('node_multiple_delete_confirm')->set(1, [ - $entities['node']->id() => [ - $entities['node']->language()->getId(), - ], - ]); - - // Needed for \Drupal\comment\Form\ConfirmDeleteMultiple. - $tempstore->get('comment_multiple_delete_confirm')->set(1, [ - $entities['comment']->id() => [ - $entities['comment']->language()->getId(), - ], - ]); - - // Needed for \Drupal\media\Form\MediaDeleteMultipleConfirmForm. - $tempstore->get('media_multiple_delete_confirm')->set(1, [ - $entities['media']->id() => [ - $entities['media']->language()->getId(), - ], - ]); - - $keyvalue_expirable = $this->container->get('keyvalue.expirable'); - - // Needed for \Drupal\system\Form\ModulesUninstallConfirmForm. - $keyvalue_expirable->get('modules_uninstall')->set(1, ['telephone']); - - // Needed for Drupal\system\Form\ModulesListConfirmForm. - $keyvalue_expirable->get('module_list')->set(1, [ - 'install' => [ - 'telephone' => 'Telephone', - ], - 'experimental' => [], - 'dependencies' => [], - ]); - } - /** * Checks each entity list form. * @@ -656,13 +669,79 @@ protected function getArguments($object, $method, array $entities) { $scalars['install_state'] = ['profiles' => []]; break; + case 'Drupal\action\ActionAddForm': + $scalars['action_id'] = 'user_block_user_action'; + break; + + case 'Drupal\node\Form\NodeRevisionDeleteForm': + case 'Drupal\node\Form\NodeRevisionRevertForm': + $scalars['node_revision'] = $entities['node']->getRevisionId(); + break; + + case 'Drupal\node\Form\NodeRevisionRevertTranslationForm': + $scalars['node_revision'] = $entities['node']->getRevisionId(); + $scalars['langcode'] = $entities['configurable_language']->id(); + break; + + case 'Drupal\node\Form\DeleteMultiple': + $scalars['entity_type_id'] = 'node'; + break; + + case 'Drupal\comment\Form\ConfirmDeleteMultiple': + $scalars['entity_type_id'] = 'comment'; + break; + + case 'Drupal\Core\Entity\Form\DeleteMultipleForm': + $scalars['entity_type_id'] = 'media'; + break; + + case 'Drupal\layout_builder\Form\AddBlockForm': + $section_storage = DefaultsSectionStorage::create($this->container, [], 'defaults', []); + $section_storage->setContext('display', EntityContext::fromEntity($entities['entity_view_display'])); + $objects['section_storage'] = $section_storage; + $scalars['delta'] = 0; + $scalars['region'] = 'main'; + $scalars['plugin_id'] = 'bar'; + break; + + case 'Drupal\layout_builder\Form\ConfigureSectionForm': + case 'Drupal\layout_builder\Form\RemoveSectionForm': + $section_storage = DefaultsSectionStorage::create($this->container, [], 'defaults', []); + $section_storage->setContext('display', EntityContext::fromEntity($entities['entity_view_display'])); + $objects['section_storage'] = $section_storage; + $scalars['delta'] = 0; + break; + + case 'Drupal\layout_builder\Form\LayoutBuilderDisableForm': + case 'Drupal\layout_builder\Form\LayoutBuilderEntityViewDisplayForm': + $section_storage = DefaultsSectionStorage::create($this->container, [], 'defaults', []); + $section_storage->setContext('display', EntityContext::fromEntity($entities['entity_view_display'])); + $objects['section_storage'] = $section_storage; + break; + + case 'Drupal\layout_builder\Form\RemoveBlockForm': + case 'Drupal\layout_builder\Form\UpdateBlockForm': + $section_storage = DefaultsSectionStorage::create($this->container, [], 'defaults', []); + $section_storage->setContext('display', EntityContext::fromEntity($entities['entity_view_display'])); + $objects['section_storage'] = $section_storage; + $scalars['delta'] = 0; + $scalars['region'] = 'main'; + $scalars['uuid'] = 'first-uuid'; + break; + + case 'Drupal\layout_builder\Form\RevertOverridesForm': + $section_storage = OverridesSectionStorage::create($this->container, [], 'overrides', []); + $section_storage->setContext('entity', EntityContext::fromEntity($entities['node'])); + $objects['section_storage'] = $section_storage; + break; + } return (new ArgumentsResolver($scalars, $objects, []))->getArguments([$object, $method]); } /** - * Handles the form state manipulation needed by the Views UI. + * Performs any operations needed to process forms. * * @param string $class * The name of the class. @@ -671,7 +750,11 @@ protected function getArguments($object, $method, array $entities) { * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. */ - protected function handleViewsFormState($class, array $entities, FormStateInterface $form_state) { + protected function prepareForms($class, array $entities, FormStateInterface $form_state) { + $keyvalue_expirable = $this->container->get('keyvalue.expirable'); + $tempstore = $this->container->get('tempstore.private'); + $state = $this->container->get('state'); + if (is_subclass_of($class, ViewsFormInterface::class)) { $form_state->set('view', $entities['view']); $form_state->set('display_id', 'default'); @@ -692,6 +775,94 @@ protected function handleViewsFormState($class, array $entities, FormStateInterf case 'Drupal\views_ui\Form\Ajax\Rearrange': $form_state->set('type', 'sort'); break; + + case 'Drupal\migrate_drupal_ui\Form\CredentialForm': + $tempstore->get('migrate_drupal_ui')->set('step', 'credential'); + break; + + case 'Drupal\migrate_drupal_ui\Form\OverviewForm': + $state->delete('migrate_drupal_ui.performed'); + break; + + case 'Drupal\migrate_drupal_ui\Form\IncrementalForm': + $tempstore->get('migrate_drupal_ui')->set('step', 'incremental'); + $state->set('migrate_drupal_ui.performed', 280299600); + break; + + case 'Drupal\migrate_drupal_ui\Form\ReviewForm': + $tempstore->get('migrate_drupal_ui')->set('step', 'review'); + $tempstore->get('migrate_drupal_ui')->set('version', 7); + $tempstore->get('migrate_drupal_ui')->set('migrations', ['foo']); + $tempstore->get('migrate_drupal_ui')->set('system_data', ['module' => ['bar' => ['status' => TRUE]]]); + break; + + case 'Drupal\ban\Form\BanDelete': + $this->container->get('ban.ip_manager')->banIp('86.7.5.309'); + break; + + case 'Drupal\system\Form\CronForm': + $state->set('system.cron_key', Crypt::randomBytesBase64(55)); + break; + + case 'Drupal\Core\Installer\Form\SelectLanguageForm': + require_once $this->root . '/core/includes/install.core.inc'; + break; + + case 'Drupal\user\Form\UserMultipleCancelConfirm': + $tempstore->get('user_user_operations_cancel')->set(1, [User::load(2)]); + break; + + case 'Drupal\node\Form\DeleteMultiple': + $tempstore->get('entity_delete_multiple_confirm')->set('1:node', [ + $entities['node']->id() => [ + $entities['node']->language()->getId(), + ], + ]); + break; + + case 'Drupal\comment\Form\ConfirmDeleteMultiple': + $tempstore->get('entity_delete_multiple_confirm')->set('1:comment', [ + $entities['comment']->id() => [ + $entities['comment']->language()->getId(), + ], + ]); + break; + + case 'Drupal\Core\Entity\Form\DeleteMultipleForm': + $tempstore->get('entity_delete_multiple_confirm')->set('1:media', [ + $entities['media']->id() => [ + $entities['media']->language()->getId(), + ], + ]); + break; + + case 'Drupal\media\Form\MediaDeleteMultipleConfirmForm': + $tempstore->get('media_multiple_delete_confirm')->set(1, [ + $entities['media']->id() => [ + $entities['media']->language()->getId(), + ], + ]); + break; + + case 'Drupal\system\Form\ModulesUninstallConfirmForm': + $keyvalue_expirable->get('modules_uninstall')->set(1, ['telephone']); + break; + + case 'Drupal\system\Form\ModulesListExperimentalConfirmForm': + case 'Drupal\system\Form\ModulesListConfirmForm': + $keyvalue_expirable->get('module_list')->set(1, [ + 'install' => [ + 'telephone' => 'Telephone', + ], + 'experimental' => [], + 'dependencies' => [], + ]); + break; + + case 'Drupal\media_library\Form\MediaLibraryUploadForm': + $this->container->get('request_stack')->getCurrentRequest()->query->set('media_library_remaining', -1); + break; + } } @@ -708,19 +879,37 @@ protected function handleViewsFormState($class, array $entities, FormStateInterf protected function checkForms(array $form_classes, array $entities, EntityInterface $entity = NULL) { $entity_form_builder = $this->container->get('entity.form_builder'); $form_builder = $this->container->get('form_builder'); + $renderer = $this->container->get('renderer'); // Perform tests on all forms. foreach ($form_classes as $operation => $class) { $form_state = new FormState(); $form_state->addBuildInfo('args', $this->buildArguments($class, 'buildForm', $entities)); - $this->handleViewsFormState($class, $entities, $form_state); + $this->prepareForms($class, $entities, $form_state); - if ($entity) { - $form = $entity_form_builder->getForm($entity, $operation, $form_state->getCacheableArray()); + // Entity forms with the 'delete-multiple-confirm' are not true entity + // forms. + if ($entity && $operation !== 'delete-multiple-confirm') { + try { + $form = $entity_form_builder->getForm($entity, $operation, $form_state->getCacheableArray()); + } + catch (\Exception $e) { + $this->fail(sprintf('Processing entity form "%s:%s" failed with exception %s: "%s"', $entity->getEntityTypeId(), $operation, get_class($e), $e->getMessage())); + continue; + } } else { - $form = $form_builder->buildForm($class, $form_state); + $form = []; + try { + $renderer->executeInRenderContext(new RenderContext(), function () use (&$form, $class, $form_state, $form_builder) { + $form = $form_builder->buildForm($class, $form_state); + }); + } + catch (\Exception $e) { + $this->fail(sprintf('Processing form "%s" failed with exception %s: "%s"', $class, get_class($e), $e->getMessage())); + continue; + } } $this->ensureFormElementTitle($form, $class); @@ -738,13 +927,13 @@ protected function checkForms(array $form_classes, array $entities, EntityInterf protected function ensureFormElementTitle(array $element, $class) { foreach (Element::children($element) as $key) { $this->ensureFormElementTitle($element[$key], $class); - - // Skip any element without a valid #type or that does not accept input. - if (!isset($element['#type']) || in_array($element['#type'], $this->typesToSkip) || empty($element['#input'])) { - continue; + } + // Skip any element without a valid #type or that does not accept input. + if (isset($element['#type']) && !in_array($element['#type'], $this->typesToSkip) && !empty($element['#input'])) { + $key = in_array($element['#type'], ['button', 'submit']) ? '#value' : '#title'; + if (empty($element['#attributes']['aria-labelledby'])) { + $this->assertArrayHasKey($key, $element, sprintf('%s: $form[%s] (#type => %s)', $class, implode('][', $element['#parents']), $element['#type'])); } - - $this->assertArrayHasKey('#title', $element, sprintf('%s: %s (%s)', $class, implode('][', $element['#parents']), $element['#type'])); } }