diff --git a/core/lib/Drupal/Core/Config/ConfigManager.php b/core/lib/Drupal/Core/Config/ConfigManager.php index 7f4a534..faa58c8 100644 --- a/core/lib/Drupal/Core/Config/ConfigManager.php +++ b/core/lib/Drupal/Core/Config/ConfigManager.php @@ -10,7 +10,7 @@ use Drupal\Component\Diff\Diff; use Drupal\Component\Serialization\Yaml; use Drupal\Core\Config\Entity\ConfigDependencyManager; -use Drupal\Core\Config\Entity\ConfigEntityDependency; +use Drupal\Core\Config\Entity\ConfigEntityInterface; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; @@ -191,50 +191,16 @@ public function createSnapshot(StorageInterface $source_storage, StorageInterfac * {@inheritdoc} */ public function uninstall($type, $name) { - // Remove all dependent configuration entities. - $extension_dependent_entities = $this->findConfigEntityDependentsAsEntities($type, array($name)); - - // Give config entities a chance to become independent of the entities we - // are going to delete. - foreach ($extension_dependent_entities as $entity) { - $entity_dependencies = $entity->getDependencies(); - if (empty($entity_dependencies)) { - // No dependent entities nothing to do. - continue; - } - // Work out if any of the entity's dependencies are going to be affected - // by the uninstall. - $affected_dependencies = array( - 'config' => array(), - 'module' => array(), - 'theme' => array(), - ); - if (isset($entity_dependencies['config'])) { - foreach ($extension_dependent_entities as $extension_dependent_entity) { - if (in_array($extension_dependent_entity->getConfigDependencyName(), $entity_dependencies['config'])) { - $affected_dependencies['config'][] = $extension_dependent_entity; - } - } - } - // Check if the extension being uninstalled is a dependency of the entity. - if (isset($entity_dependencies[$type]) && in_array($name, $entity_dependencies[$type])) { - $affected_dependencies[$type] = array($name); - } - // Inform the entity and, if the entity is changed, re-save it. - if ($entity->onDependencyRemoval($affected_dependencies)) { - $entity->save(); - } + $entities = $this->getConfigEntitiesToChangeOnDependencyRemoval($type, [$name], FALSE); + // Fix all dependent configuration entities. + /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $entity */ + foreach ($entities['update'] as $entity) { + $entity->save(); } - - // Recalculate the dependencies, some config entities may have fixed their - // dependencies on the to-be-removed entities. - $extension_dependent_entities = $this->findConfigEntityDependentsAsEntities($type, array($name)); - // Reverse the array to that entities are removed in the correct order of - // dependence. For example, this ensures that fields are removed before - // field storages. - foreach (array_reverse($extension_dependent_entities) as $extension_dependent_entity) { - $extension_dependent_entity->setUninstalling(TRUE); - $extension_dependent_entity->delete(); + // Remove all dependent configuration entities. + foreach ($entities['delete'] as $entity) { + $entity->setUninstalling(TRUE); + $entity->delete(); } $config_names = $this->configFactory->listAll($name . '.'); @@ -259,7 +225,7 @@ public function uninstall($type, $name) { /** * {@inheritdoc} */ - public function findConfigEntityDependents($type, array $names) { + public function getConfigDependencyManager() { $dependency_manager = new ConfigDependencyManager(); // This uses the configuration storage directly to avoid blowing the static // caches in the configuration factory and the configuration entity system. @@ -271,6 +237,16 @@ public function findConfigEntityDependents($type, array $names) { return isset($config['uuid']); }); $dependency_manager->setData($data); + return $dependency_manager; + } + + /** + * {@inheritdoc} + */ + public function findConfigEntityDependents($type, array $names, ConfigDependencyManager $dependency_manager = NULL) { + if (!$dependency_manager) { + $dependency_manager = $this->getConfigDependencyManager(); + } $dependencies = array(); foreach ($names as $name) { $dependencies = array_merge($dependencies, $dependency_manager->getDependentEntities($type, $name)); @@ -281,8 +257,8 @@ public function findConfigEntityDependents($type, array $names) { /** * {@inheritdoc} */ - public function findConfigEntityDependentsAsEntities($type, array $names) { - $dependencies = $this->findConfigEntityDependents($type, $names); + public function findConfigEntityDependentsAsEntities($type, array $names, ConfigDependencyManager $dependency_manager = NULL) { + $dependencies = $this->findConfigEntityDependents($type, $names, $dependency_manager); $entities = array(); $definitions = $this->entityManager->getDefinitions(); foreach ($dependencies as $config_name => $dependency) { @@ -311,6 +287,67 @@ public function findConfigEntityDependentsAsEntities($type, array $names) { /** * {@inheritdoc} */ + public function getConfigEntitiesToChangeOnDependencyRemoval($type, array $names, $dry_run = TRUE) { + // Determine the current list of dependent configuration entities and set up + // initial values. + $dependency_manager = $this->getConfigDependencyManager(); + $dependents = $this->findConfigEntityDependentsAsEntities($type, $names, $dependency_manager); + $original_dependencies = $dependents; + $update_uuids = []; + + $return = [ + 'update' => [], + 'delete' => [], + 'unchanged' => [], + ]; + + // Try to fix any dependencies and find out what will happen to the + // dependency graph. + foreach ($dependents as $dependent) { + /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $dependent */ + if ($dry_run) { + // Clone the entity so any changes do not change any static caches. + $dependent = clone $dependent; + } + if ($this->callOnDependencyRemoval($dependent, $original_dependencies, $type, $names)) { + // Recalculate dependencies and update the dependency graph data. + $dependency_manager->updateData($dependent->getConfigDependencyName(), $dependent->calculateDependencies()); + // Based on the updated data rebuild the list of dependents. + $dependents = $this->findConfigEntityDependentsAsEntities($type, $names, $dependency_manager); + // Ensure that the dependency has actually been fixed. it is possible + // that the dependent has multiple dependencies that cause it to be in + // the dependency chain. + $fixed = TRUE; + foreach ($dependents as $entity) { + if ($entity->uuid() == $dependent->uuid()) { + $fixed = FALSE; + break; + } + } + if ($fixed) { + $return['update'][] = $dependent; + $update_uuids[] = $dependent->uuid(); + } + } + } + // Now that we've fixed all the possible dependencies the remaining need to + // be deleted. + $return['delete'] = $dependents; + $delete_uuids = array_map(function($dependent) { + return $dependent->uuid(); + }, $dependents); + // Use the lists of UUIDs to filter the original list to work out which + // configuration entities are unchanged. + $return['unchanged'] = array_filter($original_dependencies, function ($dependent) use ($delete_uuids, $update_uuids) { + return !(in_array($dependent->uuid(), $delete_uuids) || in_array($dependent->uuid(), $update_uuids)); + }); + + return $return; + } + + /** + * {@inheritdoc} + */ public function supportsConfigurationEntities($collection) { return $collection == StorageInterface::DEFAULT_COLLECTION; } @@ -326,4 +363,76 @@ public function getConfigCollectionInfo() { return $this->configCollectionInfo; } + /** + * Calls an entity's onDependencyRemoval() method. + * + * A helper method to call onDependencyRemoval() with the correct list of + * affected entities. This list should only contain dependencies on the + * entity. Configuration and content entity dependencies will be converted + * into entity objects. + * + * @param \Drupal\Core\Config\Entity\ConfigEntityInterface $entity + * The entity to call onDependencyRemoval() on. + * @param \Drupal\Core\Config\Entity\ConfigEntityInterface[] $dependent_entities + * The list of dependent configuration entities. + * @param string $type + * The type of dependency being checked. Either 'module', 'theme', 'config' + * or 'content'. + * @param array $names + * The specific names to check. If $type equals 'module' or 'theme' then it + * should be a list of module names or theme names. In the case of entity it + * should be a list of full configuration object names. + * + * @return bool + * TRUE if the entity has changed as a result of calling the + * onDependencyRemoval() method, FALSE if not. + */ + protected function callOnDependencyRemoval(ConfigEntityInterface $entity, array $dependent_entities, $type, array $names) { + $entity_dependencies = $entity->getDependencies(); + if (empty($entity_dependencies)) { + // No dependent entities nothing to do. + return FALSE; + } + + $affected_dependencies = array( + 'config' => array(), + 'content' => array(), + 'module' => array(), + 'theme' => array(), + ); + + // Work out if any of the entity's dependencies are going to be affected. + if (isset($entity_dependencies[$type])) { + // Work out which dependencies the entity has in common with the provided + // $type and $names. + $affected_dependencies[$type] = array_intersect($entity_dependencies[$type], $names); + + // If the dependencies are entities we need to convert them into objects. + if ($type == 'config' || $type == 'content') { + $affected_dependencies[$type] = array_map(function ($name) use ($type) { + if ($type == 'config') { + $entity_type_id = $this->getEntityTypeIdByName($name); + } + else { + list($entity_type_id) = explode(':', $name); + } + return $this->entityManager->loadEntityByConfigTarget($entity_type_id, $name); + }, $affected_dependencies[$type]); + } + } + + // Merge any other configuration entities into the list of affected + // dependencies if necessary. + if (isset($entity_dependencies['config'])) { + foreach ($dependent_entities as $dependent_entity) { + if (in_array($dependent_entity->getConfigDependencyName(), $entity_dependencies['config'])) { + $affected_dependencies['config'][] = $dependent_entity; + } + } + } + + // Inform the entity. + return $entity->onDependencyRemoval($affected_dependencies); + } + } diff --git a/core/lib/Drupal/Core/Config/ConfigManagerInterface.php b/core/lib/Drupal/Core/Config/ConfigManagerInterface.php index 79f4a7e..1bea217 100644 --- a/core/lib/Drupal/Core/Config/ConfigManagerInterface.php +++ b/core/lib/Drupal/Core/Config/ConfigManagerInterface.php @@ -94,6 +94,16 @@ public function createSnapshot(StorageInterface $source_storage, StorageInterfac public function uninstall($type, $name); /** + * Creates and populates a ConfigDependencyManager object. + * + * The configuration dependency manager is populated with data from the active + * store. + * + * @return \Drupal\Core\Config\Entity\ConfigDependencyManager + */ + public function getConfigDependencyManager(); + + /** * Finds config entities that are dependent on extensions or entities. * * @param string $type @@ -126,6 +136,28 @@ public function findConfigEntityDependents($type, array $names); public function findConfigEntityDependentsAsEntities($type, array $names); /** + * Determines which config entities need to updated or deleted on removal of... + * + * @param string $type + * The type of dependency being checked. Either 'module', 'theme', 'config' + * or 'content'. + * @param array $names + * The specific names to check. If $type equals 'module' or 'theme' then it + * should be a list of module names or theme names. In the case of entity it + * should be a list of full configuration object names. + * @param bool $dry_run + * If set to FALSE the entities returned in the list of updates will + * modified. In order to make the changes the caller needs to save them. If + * set to TRUE the entities returned will not be modified. + * + * @return array + * An array with two keys: 'update' and 'delete'. The value of each is a + * list of configuration entities that will have that action applied when + * the supplied dependencies are removed. + */ + public function getConfigEntitiesToChangeOnDependencyRemoval($type, array $names, $dry_run = TRUE); + + /** * Determines if the provided collection supports configuration entities. * * @param string $collection diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigDependencyManager.php b/core/lib/Drupal/Core/Config/Entity/ConfigDependencyManager.php index 676a76c..df5a1d5 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigDependencyManager.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigDependencyManager.php @@ -286,4 +286,20 @@ public function setData(array $data) { return $this; } + /** + * Updates one of the lightweight ConfigEntityDependency objects. + * + * @param $name + * The configuration dependency name. + * @param $dependencies + * The configuration dependencies. + * + * @return $this + */ + public function updateData($name, $dependencies) { + $this->graph = NULL; + $this->data[$name] = new ConfigEntityDependency($name, ['dependencies' => $dependencies]); + return $this; + } + } diff --git a/core/modules/config/src/Tests/ConfigDependencyTest.php b/core/modules/config/src/Tests/ConfigDependencyTest.php index 6c7b8ec..bdc1161 100644 --- a/core/modules/config/src/Tests/ConfigDependencyTest.php +++ b/core/modules/config/src/Tests/ConfigDependencyTest.php @@ -7,6 +7,7 @@ namespace Drupal\config\Tests; +use Drupal\entity_test\Entity\EntityTest; use Drupal\simpletest\KernelTestBase; /** @@ -21,7 +22,7 @@ class ConfigDependencyTest extends KernelTestBase { * * @var array */ - public static $modules = array('system', 'config_test'); + public static $modules = array('system', 'config_test', 'entity_test', 'user'); /** * Tests that calculating dependencies for system module. @@ -189,6 +190,10 @@ public function testConfigEntityUninstall() { $this->assertFalse($storage->load('entity1'), 'Entity 1 deleted'); $this->assertFalse($storage->load('entity2'), 'Entity 2 deleted'); + // Set a more complicated test where dependencies will be fixed. + \Drupal::state()->set('config_test.fix_dependencies', array($entity1->getConfigDependencyName())); + + // Entity1 will be deleted because it depends on node. $entity1 = $storage->create( array( 'id' => 'entity1', @@ -200,6 +205,10 @@ public function testConfigEntityUninstall() { ) ); $entity1->save(); + + // Entity2 has a dependency on Entity1 but it can be fixed because + // \Drupal\config_test\Entity::onDependencyRemoval() will remove the + // dependency before config entities are deleted. $entity2 = $storage->create( array( 'id' => 'entity2', @@ -211,16 +220,109 @@ public function testConfigEntityUninstall() { ) ); $entity2->save(); - \Drupal::state()->set('config_test.fix_dependencies', array($entity1->getConfigDependencyName())); - // Test that doing a config uninstall of the node module does not delete - // entity2 since the state setting allows - // \Drupal\config_test\Entity::onDependencyRemoval() to remove the - // dependency before config entities are deleted during the uninstall. + + // Entity3 will be unchanged because it is dependent on Entity2 which can + // be fixed. + $entity3 = $storage->create( + array( + 'id' => 'entity3', + 'dependencies' => array( + 'enforced' => array( + 'config' => array($entity2->getConfigDependencyName()), + ), + ), + ) + ); + $entity3->save(); + + // Entity4's config dependency will be fixed but it will still be deleted + // because it also depends on the node module. + $entity4 = $storage->create( + array( + 'id' => 'entity4', + 'dependencies' => array( + 'enforced' => array( + 'config' => array($entity1->getConfigDependencyName()), + 'module' => array('node', 'config_test') + ), + ), + ) + ); + $entity4->save(); + + // Do a dry run using + // \Drupal\Core\Config\ConfigManager::getConfigEntitiesToChangeOnDependencyRemoval(). + $config_entities = $config_manager->getConfigEntitiesToChangeOnDependencyRemoval('module', ['node']); + $this->assertEqual($entity1->uuid(), $config_entities['delete'][1]->uuid(), 'Entity 1 will be deleted.'); + $this->assertEqual($entity2->uuid(), reset($config_entities['update'])->uuid(), 'Entity 2 will be updated.'); + $this->assertEqual($entity3->uuid(), reset($config_entities['unchanged'])->uuid(), 'Entity 3 is not changed.'); + $this->assertEqual($entity4->uuid(), $config_entities['delete'][0]->uuid(), 'Entity 4 will be deleted.'); + + // Perform the uninstall. $config_manager->uninstall('module', 'node'); + + // Test that expected actions have been performed. $this->assertFalse($storage->load('entity1'), 'Entity 1 deleted'); $entity2 = $storage->load('entity2'); $this->assertTrue($entity2, 'Entity 2 not deleted'); $this->assertEqual($entity2->calculateDependencies()['config'], array(), 'Entity 2 dependencies updated to remove dependency on Entity1.'); + $entity3 = $storage->load('entity3'); + $this->assertTrue($entity3, 'Entity 3 not deleted'); + $this->assertEqual($entity3->calculateDependencies()['config'], [$entity2->getConfigDependencyName()], 'Entity 3 still depends on Entity 2.'); + $this->assertFalse($storage->load('entity4'), 'Entity 4 deleted'); + } + + /** + * Tests getConfigEntitiesToChangeOnDependencyRemoval() with content entities. + * + * At the moment there is no runtime code that calculates configuration + * dependencies on content entity delete because this calculation is expensive + * and all content dependencies are soft. This test ensures that the code + * works for content entities. + * + * @see \Drupal\Core\Config\ConfigManager::getConfigEntitiesToChangeOnDependencyRemoval() + */ + public function testContentEntityDelete() { + $this->installEntitySchema('entity_test'); + /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */ + $config_manager = \Drupal::service('config.manager'); + + $content_entity = EntityTest::create(); + $content_entity->save(); + /** @var \Drupal\Core\Config\Entity\ConfigEntityStorage $storage */ + $storage = $this->container->get('entity.manager')->getStorage('config_test'); + $entity1 = $storage->create( + array( + 'id' => 'entity1', + 'dependencies' => array( + 'enforced' => array( + 'content' => array($content_entity->getConfigDependencyName()) + ), + ), + ) + ); + $entity1->save(); + $entity2 = $storage->create( + array( + 'id' => 'entity2', + 'dependencies' => array( + 'enforced' => array( + 'config' => array($entity1->getConfigDependencyName()) + ), + ), + ) + ); + $entity2->save(); + + // Create a configuration entity that is not in the dependency chain. + $entity3 = $storage->create(array('id' => 'entity3')); + $entity3->save(); + + $config_entities = $config_manager->getConfigEntitiesToChangeOnDependencyRemoval('content', [$content_entity->getConfigDependencyName()]); + $this->assertEqual($entity1->uuid(), $config_entities['delete'][1]->uuid(), 'Entity 1 will be deleted.'); + $this->assertEqual($entity2->uuid(), $config_entities['delete'][0]->uuid(), 'Entity 2 will be deleted.'); + $this->assertTrue(empty($config_entities['update']), 'No dependencies of the content entity will be updated.'); + $this->assertTrue(empty($config_entities['unchanged']), 'No dependencies of the content entity will be unchanged.'); } /** diff --git a/core/modules/config/tests/config_test/src/Entity/ConfigTest.php b/core/modules/config/tests/config_test/src/Entity/ConfigTest.php index d1c9a57..4a2a11b 100644 --- a/core/modules/config/tests/config_test/src/Entity/ConfigTest.php +++ b/core/modules/config/tests/config_test/src/Entity/ConfigTest.php @@ -123,6 +123,9 @@ public static function postDelete(EntityStorageInterface $storage, array $entiti */ public function onDependencyRemoval(array $dependencies) { $changed = parent::onDependencyRemoval($dependencies); + if (!isset($this->dependencies['enforced']['config'])) { + return $changed; + } $fix_deps = \Drupal::state()->get('config_test.fix_dependencies', array()); foreach ($dependencies['config'] as $entity) { if (in_array($entity->getConfigDependencyName(), $fix_deps)) { diff --git a/core/modules/system/src/Form/ModulesUninstallConfirmForm.php b/core/modules/system/src/Form/ModulesUninstallConfirmForm.php index d2974b3..ac32f54 100644 --- a/core/modules/system/src/Form/ModulesUninstallConfirmForm.php +++ b/core/modules/system/src/Form/ModulesUninstallConfirmForm.php @@ -145,43 +145,83 @@ public function buildForm(array $form, FormStateInterface $form_state) { }, $this->modules), ); - $form['entities'] = array( + // Get the dependent entities. + $entity_types = array(); + $dependent_entities = $this->configManager->getConfigEntitiesToChangeOnDependencyRemoval('module', $this->modules); + + $form['entity_updates'] = array( '#type' => 'details', - '#title' => $this->t('Affected configuration'), - '#description' => $this->t('The listed configuration will be updated if possible, or deleted.'), + '#title' => $this->t('Configuration updates'), + '#description' => $this->t('The listed configuration will be updated.'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#access' => FALSE, ); - // Get the dependent entities. - $entity_types = array(); - $dependent_entities = $this->configManager->findConfigEntityDependentsAsEntities('module', $this->modules); - foreach ($dependent_entities as $entity) { + foreach ($dependent_entities['update'] as $entity) { + /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $entity */ + $entity_type_id = $entity->getEntityTypeId(); + if (!isset($form['entity_updates'][$entity_type_id])) { + $entity_type = $this->entityManager->getDefinition($entity_type_id); + // Store the ID and label to sort the entity types and entities later. + $label = $entity_type->getLabel(); + $entity_types[$entity_type_id] = $label; + $form['entity_updates'][$entity_type_id] = array( + '#theme' => 'item_list', + '#title' => $label, + '#items' => array(), + ); + } + $form['entity_updates'][$entity_type_id]['#items'][] = $entity->label() ?: $entity->id(); + } + if (!empty($dependent_entities['update'])) { + $form['entity_updates']['#access'] = TRUE; + + // Add a weight key to the entity type sections. + asort($entity_types, SORT_FLAG_CASE); + $weight = 0; + foreach ($entity_types as $entity_type_id => $label) { + $form['entity_updates'][$entity_type_id]['#weight'] = $weight; + // Sort the list of entity labels alphabetically. + sort($form['entity_updates'][$entity_type_id]['#items'], SORT_FLAG_CASE); + $weight++; + } + } + + $form['entity_deletes'] = array( + '#type' => 'details', + '#title' => $this->t('Configuration deletes'), + '#description' => $this->t('The listed configuration will be deleted.'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#access' => FALSE, + ); + + foreach ($dependent_entities['delete'] as $entity) { $entity_type_id = $entity->getEntityTypeId(); - if (!isset($form['entities'][$entity_type_id])) { + if (!isset($form['entity_deletes'][$entity_type_id])) { $entity_type = $this->entityManager->getDefinition($entity_type_id); // Store the ID and label to sort the entity types and entities later. $label = $entity_type->getLabel(); $entity_types[$entity_type_id] = $label; - $form['entities'][$entity_type_id] = array( + $form['entity_deletes'][$entity_type_id] = array( '#theme' => 'item_list', '#title' => $label, '#items' => array(), ); } - $form['entities'][$entity_type_id]['#items'][] = $entity->label(); + $form['entity_deletes'][$entity_type_id]['#items'][] = $entity->label() ?: $entity->id(); } - if (!empty($dependent_entities)) { - $form['entities']['#access'] = TRUE; + if (!empty($dependent_entities['delete'])) { + $form['entity_deletes']['#access'] = TRUE; // Add a weight key to the entity type sections. asort($entity_types, SORT_FLAG_CASE); $weight = 0; foreach ($entity_types as $entity_type_id => $label) { - $form['entities'][$entity_type_id]['#weight'] = $weight; + $form['entity_deletes'][$entity_type_id]['#weight'] = $weight; // Sort the list of entity labels alphabetically. - sort($form['entities'][$entity_type_id]['#items'], SORT_FLAG_CASE); + sort($form['entity_deletes'][$entity_type_id]['#items'], SORT_FLAG_CASE); $weight++; } } diff --git a/core/modules/system/src/Tests/Module/UninstallTest.php b/core/modules/system/src/Tests/Module/UninstallTest.php index aa06bda..39cb7a2 100644 --- a/core/modules/system/src/Tests/Module/UninstallTest.php +++ b/core/modules/system/src/Tests/Module/UninstallTest.php @@ -45,7 +45,9 @@ function testUninstallPage() { $this->drupalLogin($account); // Create a node type. - $node_type = entity_create('node_type', array('type' => 'uninstall_blocker')); + $node_type = entity_create('node_type', array('type' => 'uninstall_blocker', 'name' => 'Uninstall blocker')); + // Create a dependency that can be fixed. + $node_type->setThirdPartySetting('module_test', 'key', 'value'); $node_type->save(); // Add a node to prevent node from being uninstalled. $node = entity_create('node', array('type' => 'uninstall_blocker')); @@ -57,13 +59,14 @@ function testUninstallPage() { $this->assertText(\Drupal::translation()->translate('The following reasons prevents Node from being uninstalled: There is content for the entity type: Content'), 'Content prevents uninstalling node module.'); // Delete the node to allow node to be uninstalled. $node->delete(); - $node_type->delete(); // Uninstall module_test. $edit = array(); $edit['uninstall[module_test]'] = TRUE; $this->drupalPostForm('admin/modules/uninstall', $edit, t('Uninstall')); - $this->assertNoText(\Drupal::translation()->translate('Affected configuration'), 'No configuration deletions listed on the module install confirmation page.'); + $this->assertNoText(\Drupal::translation()->translate('Configuration deletes'), 'No configuration deletions listed on the module install confirmation page.'); + $this->assertText(\Drupal::translation()->translate('Configuration updates'), 'Configuration updates listed on the module install confirmation page.'); + $this->assertText($node_type->label(), String::format('The entity label "!label" found.', array('!label' => $node_type->label()))); $this->drupalPostForm(NULL, NULL, t('Uninstall')); $this->assertText(t('The selected modules have been uninstalled.'), 'Modules status has been updated.'); @@ -73,11 +76,12 @@ function testUninstallPage() { $edit = array(); $edit['uninstall[node]'] = TRUE; $this->drupalPostForm('admin/modules/uninstall', $edit, t('Uninstall')); - $this->assertText(\Drupal::translation()->translate('Affected configuration'), 'Configuration deletions listed on the module install confirmation page.'); + $this->assertText(\Drupal::translation()->translate('Configuration deletes'), 'Configuration deletions listed on the module install confirmation page.'); + $this->assertNoText(\Drupal::translation()->translate('Configuration updates'), 'No configuration updates listed on the module install confirmation page.'); $entity_types = array(); foreach ($node_dependencies as $entity) { - $label = $entity->label(); + $label = $entity->label() ?: $entity->id(); $this->assertText($label, String::format('The entity label "!label" found.', array('!label' => $label))); $entity_types[] = $entity->getEntityTypeId(); } diff --git a/core/modules/system/tests/modules/module_test/config/schema/module_test.schema.yml b/core/modules/system/tests/modules/module_test/config/schema/module_test.schema.yml new file mode 100644 index 0000000..a44a1b9 --- /dev/null +++ b/core/modules/system/tests/modules/module_test/config/schema/module_test.schema.yml @@ -0,0 +1,2 @@ +node.type.*.third_party.module_test: + type: ignore