Initial Drupal 11 with DDEV setup
This commit is contained in:
@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\FunctionalTests\Entity;
|
||||
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the correct mapping of user input on the correct field delta elements.
|
||||
*
|
||||
* @group Entity
|
||||
*/
|
||||
class ContentEntityFormCorrectUserInputMappingOnFieldDeltaElementsTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* The ID of the type of the entity under test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $entityTypeId;
|
||||
|
||||
/**
|
||||
* The field name with multiple properties being test with the entity type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['entity_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$web_user = $this->drupalCreateUser(['administer entity_test content']);
|
||||
$this->drupalLogin($web_user);
|
||||
|
||||
// Create a field of field type "shape" with unlimited cardinality on the
|
||||
// entity type "entity_test".
|
||||
$this->entityTypeId = 'entity_test';
|
||||
$this->fieldName = 'shape';
|
||||
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => $this->fieldName,
|
||||
'entity_type' => $this->entityTypeId,
|
||||
'type' => 'shape',
|
||||
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
|
||||
])
|
||||
->save();
|
||||
FieldConfig::create([
|
||||
'entity_type' => $this->entityTypeId,
|
||||
'field_name' => $this->fieldName,
|
||||
'bundle' => $this->entityTypeId,
|
||||
'label' => 'Shape',
|
||||
'translatable' => FALSE,
|
||||
])
|
||||
->save();
|
||||
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay($this->entityTypeId, $this->entityTypeId)
|
||||
->setComponent($this->fieldName, ['type' => 'shape_only_color_editable_widget'])
|
||||
->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the correct user input mapping on complex fields.
|
||||
*/
|
||||
public function testCorrectUserInputMappingOnComplexFields(): void {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
|
||||
$storage = $this->container->get('entity_type.manager')->getStorage($this->entityTypeId);
|
||||
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
$entity = $storage->create([
|
||||
$this->fieldName => [
|
||||
['shape' => 'rectangle', 'color' => 'green'],
|
||||
['shape' => 'circle', 'color' => 'blue'],
|
||||
],
|
||||
]);
|
||||
$entity->save();
|
||||
|
||||
$this->drupalGet($this->entityTypeId . '/manage/' . $entity->id() . '/edit');
|
||||
|
||||
// Rearrange the field items.
|
||||
$edit = [
|
||||
"$this->fieldName[0][_weight]" => 0,
|
||||
"$this->fieldName[1][_weight]" => -1,
|
||||
];
|
||||
// Executing an ajax call is important before saving as it will trigger
|
||||
// form state caching and so if for any reasons the form is rebuilt with
|
||||
// the entity built based on the user submitted values with already
|
||||
// reordered field items then the correct mapping will break after the form
|
||||
// builder maps over the new form the user submitted values based on the
|
||||
// previous delta ordering.
|
||||
//
|
||||
// This is how currently the form building process works and this test
|
||||
// ensures the correct behavior no matter what changes would be made to the
|
||||
// form builder or the content entity forms.
|
||||
$this->submitForm($edit, 'Add another item');
|
||||
$this->submitForm([], 'Save');
|
||||
|
||||
// Reload the entity.
|
||||
$entity = $storage->load($entity->id());
|
||||
|
||||
// Assert that after rearranging the field items the user input will be
|
||||
// mapped on the correct delta field items.
|
||||
$this->assertEquals([
|
||||
['shape' => 'circle', 'color' => 'blue'],
|
||||
['shape' => 'rectangle', 'color' => 'green'],
|
||||
], $entity->get($this->fieldName)->getValue());
|
||||
|
||||
$this->drupalGet($this->entityTypeId . '/manage/' . $entity->id() . '/edit');
|
||||
|
||||
// Delete one of the field items and ensure that the user input is mapped on
|
||||
// the correct delta field items.
|
||||
$edit = [
|
||||
"$this->fieldName[0][_weight]" => 0,
|
||||
"$this->fieldName[1][_weight]" => -1,
|
||||
];
|
||||
$this->submitForm($edit, "{$this->fieldName}_0_remove_button");
|
||||
$this->submitForm([], 'Save');
|
||||
|
||||
$entity = $storage->load($entity->id());
|
||||
$this->assertEquals([
|
||||
['shape' => 'rectangle', 'color' => 'green'],
|
||||
], $entity->get($this->fieldName)->getValue());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\FunctionalTests\Entity;
|
||||
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\TestFileCreationTrait;
|
||||
|
||||
/**
|
||||
* Tests field validation filtering on content entity forms.
|
||||
*
|
||||
* @group Entity
|
||||
*/
|
||||
class ContentEntityFormFieldValidationFilteringTest extends BrowserTestBase {
|
||||
|
||||
use TestFileCreationTrait;
|
||||
|
||||
/**
|
||||
* The ID of the type of the entity under test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $entityTypeId;
|
||||
|
||||
/**
|
||||
* The single-valued field name being tested with the entity type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldNameSingle;
|
||||
|
||||
/**
|
||||
* The multi-valued field name being tested with the entity type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldNameMultiple;
|
||||
|
||||
/**
|
||||
* The name of the file field being tested with the entity type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldNameFile;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['entity_test', 'field_test', 'file', 'image'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$web_user = $this->drupalCreateUser(['administer entity_test content']);
|
||||
$this->drupalLogin($web_user);
|
||||
|
||||
// Create two fields of field type "test_field", one with single cardinality
|
||||
// and one with unlimited cardinality on the entity type "entity_test". It
|
||||
// is important to use this field type because its default widget has a
|
||||
// custom \Drupal\Core\Field\WidgetInterface::errorElement() implementation.
|
||||
$this->entityTypeId = 'entity_test';
|
||||
$this->fieldNameSingle = 'test_single';
|
||||
$this->fieldNameMultiple = 'test_multiple';
|
||||
$this->fieldNameFile = 'test_file';
|
||||
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => $this->fieldNameSingle,
|
||||
'entity_type' => $this->entityTypeId,
|
||||
'type' => 'test_field',
|
||||
'cardinality' => 1,
|
||||
])->save();
|
||||
FieldConfig::create([
|
||||
'entity_type' => $this->entityTypeId,
|
||||
'field_name' => $this->fieldNameSingle,
|
||||
'bundle' => $this->entityTypeId,
|
||||
'label' => 'Test single',
|
||||
'required' => TRUE,
|
||||
'translatable' => FALSE,
|
||||
])->save();
|
||||
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => $this->fieldNameMultiple,
|
||||
'entity_type' => $this->entityTypeId,
|
||||
'type' => 'test_field',
|
||||
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
|
||||
])->save();
|
||||
FieldConfig::create([
|
||||
'entity_type' => $this->entityTypeId,
|
||||
'field_name' => $this->fieldNameMultiple,
|
||||
'bundle' => $this->entityTypeId,
|
||||
'label' => 'Test multiple',
|
||||
'translatable' => FALSE,
|
||||
])->save();
|
||||
|
||||
// Also create a file field to test its '#limit_validation_errors'
|
||||
// implementation.
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => $this->fieldNameFile,
|
||||
'entity_type' => $this->entityTypeId,
|
||||
'type' => 'file',
|
||||
'cardinality' => 1,
|
||||
])->save();
|
||||
FieldConfig::create([
|
||||
'entity_type' => $this->entityTypeId,
|
||||
'field_name' => $this->fieldNameFile,
|
||||
'bundle' => $this->entityTypeId,
|
||||
'label' => 'Test file',
|
||||
'translatable' => FALSE,
|
||||
])->save();
|
||||
|
||||
$this->container->get('entity_display.repository')
|
||||
->getFormDisplay($this->entityTypeId, $this->entityTypeId, 'default')
|
||||
->setComponent($this->fieldNameSingle, ['type' => 'test_field_widget'])
|
||||
->setComponent($this->fieldNameMultiple, ['type' => 'test_field_widget'])
|
||||
->setComponent($this->fieldNameFile, ['type' => 'file_generic'])
|
||||
->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests field widgets with #limit_validation_errors.
|
||||
*/
|
||||
public function testFieldWidgetsWithLimitedValidationErrors(): void {
|
||||
$assert_session = $this->assertSession();
|
||||
$this->drupalGet($this->entityTypeId . '/add');
|
||||
|
||||
// The 'Test multiple' field is the only multi-valued field in the form, so
|
||||
// try to add a new item for it. This tests the '#limit_validation_errors'
|
||||
// property set by \Drupal\Core\Field\WidgetBase::formMultipleElements().
|
||||
$assert_session->elementsCount('css', 'div#edit-test-multiple-wrapper div.js-form-type-textfield input', 1);
|
||||
$this->submitForm([], 'Add another item');
|
||||
$assert_session->elementsCount('css', 'div#edit-test-multiple-wrapper div.js-form-type-textfield input', 2);
|
||||
|
||||
// Now try to upload a file. This tests the '#limit_validation_errors'
|
||||
// property set by
|
||||
// \Drupal\file\Plugin\Field\FieldWidget\FileWidget::process().
|
||||
$text_file = current($this->getTestFiles('text'));
|
||||
$edit = [
|
||||
'files[test_file_0]' => \Drupal::service('file_system')->realpath($text_file->uri),
|
||||
];
|
||||
$assert_session->elementNotExists('css', 'input#edit-test-file-0-remove-button');
|
||||
$this->submitForm($edit, 'Upload');
|
||||
$assert_session->elementExists('css', 'input#edit-test-file-0-remove-button');
|
||||
|
||||
// Make the 'Test multiple' field required and check that adding another
|
||||
// item does not throw a validation error.
|
||||
$field_config = FieldConfig::loadByName($this->entityTypeId, $this->entityTypeId, $this->fieldNameMultiple);
|
||||
$field_config->setRequired(TRUE);
|
||||
$field_config->save();
|
||||
|
||||
$this->drupalGet($this->entityTypeId . '/add');
|
||||
$this->submitForm([], 'Add another item');
|
||||
$assert_session->pageTextNotContains('Test multiple (value 1) field is required.');
|
||||
|
||||
// Check that saving the form without entering any value for the required
|
||||
// field still throws the proper validation errors.
|
||||
$this->submitForm([], 'Save');
|
||||
$assert_session->pageTextContains('Test single field is required.');
|
||||
$assert_session->pageTextContains('Test multiple (value 1) field is required.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\FunctionalTests\Entity;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTestBundle;
|
||||
use Drupal\entity_test\Entity\EntityTestMulRevPub;
|
||||
use Drupal\entity_test\Entity\EntityTestRev;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the delete multiple confirmation form.
|
||||
*
|
||||
* @group Entity
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
*/
|
||||
class DeleteMultipleFormTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $account;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['entity_test', 'user', 'language'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
EntityTestBundle::create([
|
||||
'id' => 'default',
|
||||
'label' => 'Default',
|
||||
])->save();
|
||||
$this->account = $this->drupalCreateUser([
|
||||
'administer entity_test content',
|
||||
]);
|
||||
$this->drupalLogin($this->account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the delete form for translatable entities.
|
||||
*/
|
||||
public function testTranslatableEntities(): void {
|
||||
ConfigurableLanguage::createFromLangcode('es')->save();
|
||||
ConfigurableLanguage::createFromLangcode('fr')->save();
|
||||
|
||||
$selection = [];
|
||||
|
||||
$entity1 = EntityTestMulRevPub::create(['type' => 'default', 'name' => 'entity1']);
|
||||
$entity1->addTranslation('es', ['name' => 'entity1 spanish']);
|
||||
$entity1->addTranslation('fr', ['name' => 'entity1 french']);
|
||||
$entity1->save();
|
||||
$selection[$entity1->id()]['en'] = 'en';
|
||||
|
||||
$entity2 = EntityTestMulRevPub::create(['type' => 'default', 'name' => 'entity2']);
|
||||
$entity2->addTranslation('es', ['name' => 'entity2 spanish']);
|
||||
$entity2->addTranslation('fr', ['name' => 'entity2 french']);
|
||||
$entity2->save();
|
||||
$selection[$entity2->id()]['es'] = 'es';
|
||||
$selection[$entity2->id()]['fr'] = 'fr';
|
||||
|
||||
$entity3 = EntityTestMulRevPub::create(['type' => 'default', 'name' => 'entity3']);
|
||||
$entity3->addTranslation('es', ['name' => 'entity3 spanish']);
|
||||
$entity3->addTranslation('fr', ['name' => 'entity3 french']);
|
||||
$entity3->save();
|
||||
$selection[$entity3->id()]['fr'] = 'fr';
|
||||
|
||||
// This entity will be inaccessible because of
|
||||
// Drupal\entity_test\EntityTestAccessControlHandler.
|
||||
$entity4 = EntityTestMulRevPub::create(['type' => 'default', 'name' => 'forbid_access']);
|
||||
$entity4->save();
|
||||
$selection[$entity4->id()]['en'] = 'en';
|
||||
|
||||
// Add the selection to the tempstore just like DeleteAction would.
|
||||
$tempstore = \Drupal::service('tempstore.private')->get('entity_delete_multiple_confirm');
|
||||
$tempstore->set($this->account->id() . ':entity_test_mulrevpub', $selection);
|
||||
|
||||
$this->drupalGet('/entity_test/delete');
|
||||
$assert = $this->assertSession();
|
||||
$assert->statusCodeEquals(200);
|
||||
$assert->elementTextContains('css', 'h1', 'Are you sure you want to delete these test entity - revisions, data table, and published interface entities?');
|
||||
$list_selector = '#entity-test-mulrevpub-delete-multiple-confirm-form > ul[data-drupal-selector="edit-entities"]';
|
||||
$assert->elementTextContains('css', $list_selector, 'entity1 (Original translation) - The following test entity - revisions, data table, and published interface translations will be deleted:');
|
||||
$assert->elementTextContains('css', $list_selector, 'entity2 spanish');
|
||||
$assert->elementTextContains('css', $list_selector, 'entity2 french');
|
||||
$assert->elementTextNotContains('css', $list_selector, 'entity3 spanish');
|
||||
$assert->elementTextContains('css', $list_selector, 'entity3 french');
|
||||
$delete_button = $this->getSession()->getPage()->findButton('Delete');
|
||||
$delete_button->click();
|
||||
$assert = $this->assertSession();
|
||||
$assert->addressEquals('/user/' . $this->account->id());
|
||||
$assert->responseContains('Deleted 6 items.');
|
||||
$assert->responseContains('1 item has not been deleted because you do not have the necessary permissions.');
|
||||
|
||||
\Drupal::entityTypeManager()->getStorage('entity_test_mulrevpub')->resetCache();
|
||||
$remaining_entities = EntityTestMulRevPub::loadMultiple([$entity1->id(), $entity2->id(), $entity3->id(), $entity4->id()]);
|
||||
$this->assertCount(3, $remaining_entities);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the delete form for untranslatable entities.
|
||||
*/
|
||||
public function testUntranslatableEntities(): void {
|
||||
$selection = [];
|
||||
|
||||
$entity1 = EntityTestRev::create(['type' => 'default', 'name' => 'entity1']);
|
||||
$entity1->save();
|
||||
$selection[$entity1->id()]['en'] = 'en';
|
||||
|
||||
$entity2 = EntityTestRev::create(['type' => 'default', 'name' => 'entity2']);
|
||||
$entity2->save();
|
||||
$selection[$entity2->id()]['en'] = 'en';
|
||||
|
||||
// This entity will be inaccessible because of
|
||||
// Drupal\entity_test\EntityTestAccessControlHandler.
|
||||
$entity3 = EntityTestRev::create(['type' => 'default', 'name' => 'forbid_access']);
|
||||
$entity3->save();
|
||||
$selection[$entity3->id()]['en'] = 'en';
|
||||
|
||||
// This entity will be inaccessible because of
|
||||
// Drupal\entity_test\EntityTestAccessControlHandler.
|
||||
$entity4 = EntityTestRev::create(['type' => 'default', 'name' => 'forbid_access']);
|
||||
$entity4->save();
|
||||
$selection[$entity4->id()]['en'] = 'en';
|
||||
|
||||
// Add the selection to the tempstore just like DeleteAction would.
|
||||
$tempstore = \Drupal::service('tempstore.private')->get('entity_delete_multiple_confirm');
|
||||
$tempstore->set($this->account->id() . ':entity_test_rev', $selection);
|
||||
|
||||
$this->drupalGet('/entity_test_rev/delete_multiple');
|
||||
$assert = $this->assertSession();
|
||||
$assert->statusCodeEquals(200);
|
||||
$assert->elementTextContains('css', 'h1', 'Are you sure you want to delete these test entity - revisions entities?');
|
||||
$list_selector = '#entity-test-rev-delete-multiple-confirm-form > ul[data-drupal-selector="edit-entities"]';
|
||||
$assert->elementTextContains('css', $list_selector, 'entity1');
|
||||
$assert->elementTextContains('css', $list_selector, 'entity2');
|
||||
$delete_button = $this->getSession()->getPage()->findButton('Delete');
|
||||
$delete_button->click();
|
||||
$assert = $this->assertSession();
|
||||
$assert->addressEquals('/user/' . $this->account->id());
|
||||
$assert->responseContains('Deleted 2 items.');
|
||||
$assert->responseContains('2 items have not been deleted because you do not have the necessary permissions.');
|
||||
|
||||
\Drupal::entityTypeManager()->getStorage('entity_test_mulrevpub')->resetCache();
|
||||
$remaining_entities = EntityTestRev::loadMultiple([$entity1->id(), $entity2->id(), $entity3->id(), $entity4->id()]);
|
||||
$this->assertCount(2, $remaining_entities);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\FunctionalTests\Entity;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\entity_test\Entity\EntityTestBundle;
|
||||
use Drupal\entity_test\Entity\EntityTestWithBundle;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\system\Functional\Cache\AssertPageCacheContextsAndTagsTrait;
|
||||
|
||||
/**
|
||||
* Tests that bundle tags are invalidated when entities change.
|
||||
*
|
||||
* @group Entity
|
||||
*/
|
||||
class EntityBundleListCacheTest extends BrowserTestBase {
|
||||
|
||||
use AssertPageCacheContextsAndTagsTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['cache_test', 'entity_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
EntityTestBundle::create([
|
||||
'id' => 'bundle_a',
|
||||
'label' => 'Bundle A',
|
||||
])->save();
|
||||
EntityTestBundle::create([
|
||||
'id' => 'bundle_b',
|
||||
'label' => 'Bundle B',
|
||||
])->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that tags are invalidated when an entity with that bundle changes.
|
||||
*/
|
||||
public function testBundleListingCache(): void {
|
||||
// Access to lists of test entities with each bundle.
|
||||
$bundle_a_url = Url::fromRoute('cache_test_list.bundle_tags', ['entity_type_id' => 'entity_test_with_bundle', 'bundle' => 'bundle_a']);
|
||||
$bundle_b_url = Url::fromRoute('cache_test_list.bundle_tags', ['entity_type_id' => 'entity_test_with_bundle', 'bundle' => 'bundle_b']);
|
||||
$this->drupalGet($bundle_a_url);
|
||||
$this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'MISS');
|
||||
$this->assertCacheTags(['rendered', 'entity_test_with_bundle_list:bundle_a']);
|
||||
|
||||
$this->drupalGet($bundle_a_url);
|
||||
$this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'HIT');
|
||||
$this->assertCacheTags(['rendered', 'entity_test_with_bundle_list:bundle_a']);
|
||||
$this->drupalGet($bundle_b_url);
|
||||
$this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'MISS');
|
||||
$this->assertCacheTags(['rendered', 'entity_test_with_bundle_list:bundle_b']);
|
||||
$this->drupalGet($bundle_b_url);
|
||||
$this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'HIT');
|
||||
$entity1 = EntityTestWithBundle::create(['type' => 'bundle_a', 'name' => 'entity1']);
|
||||
$entity1->save();
|
||||
// Check that tags are invalidated after creating an entity of the current
|
||||
// bundle.
|
||||
$this->drupalGet($bundle_a_url);
|
||||
$this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'MISS');
|
||||
$this->drupalGet($bundle_a_url);
|
||||
$this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'HIT');
|
||||
// Check that tags are not invalidated after creating an entity of a
|
||||
// different bundle than the current in the request.
|
||||
$this->drupalGet($bundle_b_url);
|
||||
$this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'HIT');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\FunctionalTests\Entity;
|
||||
|
||||
use Drupal\Component\Uuid\Uuid;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\content_translation\Traits\ContentTranslationTestTrait;
|
||||
|
||||
/**
|
||||
* Tests that an entity with a UUID as ID can be managed.
|
||||
*
|
||||
* @group Entity
|
||||
*/
|
||||
class EntityUuidIdTest extends BrowserTestBase {
|
||||
|
||||
use ContentTranslationTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['block', 'content_translation', 'entity_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->createLanguageFromLangcode('af');
|
||||
$this->enableContentTranslation('entity_test_uuid_id', 'entity_test_uuid_id');
|
||||
$this->drupalPlaceBlock('page_title_block');
|
||||
$this->drupalPlaceBlock('local_tasks_block');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the user interface for the test entity.
|
||||
*/
|
||||
public function testUi(): void {
|
||||
$this->drupalLogin($this->createUser([
|
||||
'administer entity_test content',
|
||||
'create content translations',
|
||||
'translate entity_test_uuid_id',
|
||||
'view test entity',
|
||||
]));
|
||||
|
||||
// Test adding an entity.
|
||||
$this->drupalGet('/entity_test_uuid_id/add');
|
||||
$this->submitForm([
|
||||
'Name' => 'Test entity with UUID ID',
|
||||
], 'Save');
|
||||
$this->assertSession()->elementTextEquals('css', 'h1', 'Edit Test entity with UUID ID');
|
||||
$this->assertSession()->addressMatches('#^/entity_test_uuid_id/manage/' . Uuid::VALID_PATTERN . '/edit$#');
|
||||
|
||||
// Test translating an entity.
|
||||
$this->clickLink('Translate');
|
||||
$this->clickLink('Add');
|
||||
$this->submitForm([
|
||||
'Name' => 'Afrikaans translation of test entity with UUID ID',
|
||||
], 'Save');
|
||||
$this->assertSession()->elementTextEquals('css', 'h1', 'Afrikaans translation of test entity with UUID ID [Afrikaans translation]');
|
||||
$this->assertSession()->addressMatches('#^/af/entity_test_uuid_id/manage/' . Uuid::VALID_PATTERN . '/edit$#');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,389 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\FunctionalTests\Entity;
|
||||
|
||||
use Drupal\Core\Entity\RevisionLogInterface;
|
||||
use Drupal\entity_test\Entity\EntityTestRev;
|
||||
use Drupal\entity_test\Entity\EntityTestRevPub;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests deleting a revision with revision delete form.
|
||||
*
|
||||
* @group Entity
|
||||
* @group #slow
|
||||
* @coversDefaultClass \Drupal\Core\Entity\Form\RevisionDeleteForm
|
||||
*/
|
||||
class RevisionDeleteFormTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'block',
|
||||
'entity_test',
|
||||
'entity_test_revlog',
|
||||
'dblog',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->drupalPlaceBlock('page_title_block');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests title by whether entity supports revision creation dates.
|
||||
*/
|
||||
public function testPageTitle(): void {
|
||||
foreach (static::providerPageTitle() as $cases) {
|
||||
[$entityTypeId, $expectedQuestion] = $cases;
|
||||
$this->doTestPageTitle($entityTypeId, $expectedQuestion);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests title by whether entity supports revision creation dates.
|
||||
*
|
||||
* @param string $entityTypeId
|
||||
* The entity type to test.
|
||||
* @param string $expectedQuestion
|
||||
* The expected question/page title.
|
||||
*
|
||||
* @covers ::getQuestion
|
||||
*/
|
||||
protected function doTestPageTitle(string $entityTypeId, string $expectedQuestion): void {
|
||||
/** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
|
||||
$storage = \Drupal::entityTypeManager()->getStorage($entityTypeId);
|
||||
|
||||
$entity = $storage->create([
|
||||
'type' => $entityTypeId,
|
||||
'name' => 'delete revision',
|
||||
]);
|
||||
if ($entity instanceof RevisionLogInterface) {
|
||||
$date = new \DateTime('11 January 2009 4:00:00pm');
|
||||
$entity->setRevisionCreationTime($date->getTimestamp());
|
||||
}
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
$revisionId = $entity->getRevisionId();
|
||||
|
||||
// Create a new latest revision.
|
||||
if ($entity instanceof RevisionLogInterface) {
|
||||
$entity->setRevisionCreationTime($date->modify('+1 hour')->getTimestamp());
|
||||
}
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
|
||||
// Reload the entity.
|
||||
$revision = $storage->loadRevision($revisionId);
|
||||
$this->drupalGet($revision->toUrl('revision-delete-form'));
|
||||
$this->assertSession()->pageTextContains($expectedQuestion);
|
||||
$this->assertSession()->buttonExists('Delete');
|
||||
$this->assertSession()->linkExists('Cancel');
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testPageTitle.
|
||||
*/
|
||||
public static function providerPageTitle(): array {
|
||||
return [
|
||||
['entity_test_rev', 'Are you sure you want to delete the revision?'],
|
||||
['entity_test_revlog', 'Are you sure you want to delete the revision from Sun, 11 Jan 2009 - 16:00?'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test cannot delete latest revision.
|
||||
*
|
||||
* @covers \Drupal\Core\Entity\EntityAccessControlHandler::checkAccess
|
||||
*/
|
||||
public function testAccessDeleteLatestDefault(): void {
|
||||
/** @var \Drupal\entity_test\Entity\EntityTestRev $entity */
|
||||
$entity = EntityTestRev::create();
|
||||
$entity->setName('delete revision');
|
||||
$entity->save();
|
||||
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
|
||||
$this->drupalGet($entity->toUrl('revision-delete-form'));
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that revisions can and can't be deleted in various scenarios.
|
||||
*/
|
||||
public function testAccessDelete(): void {
|
||||
$this->testAccessDeleteLatestForwardRevision();
|
||||
$this->testAccessDeleteDefault();
|
||||
$this->testAccessDeleteNonLatest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that forward revision can be deleted.
|
||||
*
|
||||
* @covers \Drupal\Core\Entity\EntityAccessControlHandler::checkAccess
|
||||
*/
|
||||
protected function testAccessDeleteLatestForwardRevision(): void {
|
||||
/** @var \Drupal\entity_test\Entity\EntityTestRevPub $entity */
|
||||
$entity = EntityTestRevPub::create();
|
||||
$entity->setName('delete revision');
|
||||
$entity->save();
|
||||
|
||||
$entity->isDefaultRevision(TRUE);
|
||||
$entity->setPublished();
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
|
||||
$entity->isDefaultRevision(FALSE);
|
||||
$entity->setUnpublished();
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
|
||||
$this->drupalGet($entity->toUrl('revision-delete-form'));
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertTrue($entity->access('delete revision', $this->rootUser, FALSE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test cannot delete default revision.
|
||||
*
|
||||
* @covers \Drupal\Core\Entity\EntityAccessControlHandler::checkAccess
|
||||
*/
|
||||
protected function testAccessDeleteDefault(): void {
|
||||
/** @var \Drupal\entity_test\Entity\EntityTestRevPub $entity */
|
||||
$entity = EntityTestRevPub::create();
|
||||
$entity->setName('delete revision');
|
||||
$entity->save();
|
||||
|
||||
$entity->isDefaultRevision(TRUE);
|
||||
$entity->setPublished();
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
$revisionId = $entity->getRevisionId();
|
||||
|
||||
$entity->isDefaultRevision(FALSE);
|
||||
$entity->setUnpublished();
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
|
||||
// Reload the entity.
|
||||
/** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
|
||||
$storage = \Drupal::entityTypeManager()->getStorage('entity_test_revpub');
|
||||
/** @var \Drupal\entity_test\Entity\EntityTestRevPub $revision */
|
||||
$revision = $storage->loadRevision($revisionId);
|
||||
// Check default but not latest.
|
||||
$this->assertTrue($revision->isDefaultRevision());
|
||||
$this->assertFalse($revision->isLatestRevision());
|
||||
$this->drupalGet($revision->toUrl('revision-delete-form'));
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
$this->assertFalse($revision->access('delete revision', $this->rootUser, FALSE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test can delete non-latest revision.
|
||||
*
|
||||
* @covers \Drupal\Core\Entity\EntityAccessControlHandler::checkAccess
|
||||
*/
|
||||
protected function testAccessDeleteNonLatest(): void {
|
||||
/** @var \Drupal\entity_test\Entity\EntityTestRev $entity */
|
||||
$entity = EntityTestRev::create();
|
||||
$entity->setName('delete revision');
|
||||
$entity->save();
|
||||
$entity->isDefaultRevision();
|
||||
$revisionId = $entity->getRevisionId();
|
||||
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
|
||||
// Reload the entity.
|
||||
/** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
|
||||
$storage = \Drupal::entityTypeManager()->getStorage('entity_test_rev');
|
||||
$revision = $storage->loadRevision($revisionId);
|
||||
$this->drupalGet($revision->toUrl('revision-delete-form'));
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertTrue($revision->access('delete revision', $this->rootUser, FALSE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests revision deletion form.
|
||||
*/
|
||||
public function testSubmitForm(): void {
|
||||
foreach (static::providerSubmitForm() as $case) {
|
||||
[$permissions, $entityTypeId, $entityLabel, $totalRevisions, $expectedLog, $expectedMessage, $expectedDestination] = $case;
|
||||
$this->doTestSubmitForm($permissions, $entityTypeId, $entityLabel, $totalRevisions, $expectedLog, $expectedMessage, $expectedDestination);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests revision deletion, and expected response after deletion.
|
||||
*
|
||||
* @param array $permissions
|
||||
* If not empty, a user will be created and logged in with these
|
||||
* permissions.
|
||||
* @param string $entityTypeId
|
||||
* The entity type to test.
|
||||
* @param string $entityLabel
|
||||
* The entity label, which corresponds to access grants.
|
||||
* @param int $totalRevisions
|
||||
* Total number of revisions to create.
|
||||
* @param string $expectedLog
|
||||
* Expected log.
|
||||
* @param string $expectedMessage
|
||||
* Expected messenger message.
|
||||
* @param string|int $expectedDestination
|
||||
* Expected destination after deletion.
|
||||
*
|
||||
* @covers ::submitForm
|
||||
*/
|
||||
protected function doTestSubmitForm(array $permissions, string $entityTypeId, string $entityLabel, int $totalRevisions, array $expectedLog, string $expectedMessage, $expectedDestination): void {
|
||||
if (count($permissions) > 0) {
|
||||
$this->drupalLogin($this->createUser($permissions));
|
||||
}
|
||||
/** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
|
||||
$storage = \Drupal::entityTypeManager()->getStorage($entityTypeId);
|
||||
|
||||
$entity = $storage->create([
|
||||
'type' => $entityTypeId,
|
||||
'name' => $entityLabel,
|
||||
]);
|
||||
if ($entity instanceof RevisionLogInterface) {
|
||||
$date = new \DateTime('11 January 2009 4:00:00pm');
|
||||
$entity->setRevisionCreationTime($date->getTimestamp());
|
||||
}
|
||||
$entity->save();
|
||||
$revisionId = $entity->getRevisionId();
|
||||
|
||||
$otherRevisionIds = [];
|
||||
for ($i = 0; $i < $totalRevisions - 1; $i++) {
|
||||
if ($entity instanceof RevisionLogInterface) {
|
||||
$entity->setRevisionCreationTime($date->modify('+1 hour')->getTimestamp());
|
||||
}
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
$otherRevisionIds[] = $entity->getRevisionId();
|
||||
}
|
||||
|
||||
$revision = $storage->loadRevision($revisionId);
|
||||
$this->drupalGet($revision->toUrl('revision-delete-form'));
|
||||
$this->submitForm([], 'Delete');
|
||||
|
||||
// The revision was deleted.
|
||||
$this->assertNull($storage->loadRevision($revisionId));
|
||||
// Make sure the other revisions were not deleted.
|
||||
foreach ($otherRevisionIds as $otherRevisionId) {
|
||||
$this->assertNotNull($storage->loadRevision($otherRevisionId));
|
||||
}
|
||||
|
||||
// Destination.
|
||||
if ($expectedDestination === 404) {
|
||||
$this->assertSession()->statusCodeEquals(404);
|
||||
}
|
||||
else {
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->addressEquals($expectedDestination);
|
||||
}
|
||||
|
||||
// Logger log.
|
||||
$logs = $this->getLogs($entity->getEntityType()->getProvider());
|
||||
$this->assertCount(1, $logs);
|
||||
$this->assertEquals("@type: deleted %title revision %revision.", $logs[0]->message);
|
||||
$this->assertEquals($expectedLog, unserialize($logs[0]->variables));
|
||||
// Messenger message.
|
||||
$this->assertSession()->pageTextContains($expectedMessage);
|
||||
\Drupal::database()->delete('watchdog')->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testSubmitForm.
|
||||
*/
|
||||
public static function providerSubmitForm(): array {
|
||||
$data = [];
|
||||
|
||||
$data['not supporting revision log, one revision remaining after delete, no view access'] = [
|
||||
[],
|
||||
'entity_test_rev',
|
||||
'view all revisions, delete revision',
|
||||
2,
|
||||
[
|
||||
'@type' => 'entity_test_rev',
|
||||
'%title' => 'view all revisions, delete revision',
|
||||
'%revision' => '1',
|
||||
],
|
||||
'Revision of Entity Test Bundle view all revisions, delete revision has been deleted.',
|
||||
'/entity_test_rev/1/revisions',
|
||||
];
|
||||
|
||||
$data['not supporting revision log, one revision remaining after delete, view access'] = [
|
||||
['view test entity'],
|
||||
'entity_test_rev',
|
||||
'view, view all revisions, delete revision',
|
||||
2,
|
||||
[
|
||||
'@type' => 'entity_test_rev',
|
||||
'%title' => 'view, view all revisions, delete revision',
|
||||
'%revision' => '3',
|
||||
],
|
||||
'Revision of Entity Test Bundle view, view all revisions, delete revision has been deleted.',
|
||||
'/entity_test_rev/2/revisions',
|
||||
];
|
||||
|
||||
$data['supporting revision log, one revision remaining after delete, no view access'] = [
|
||||
[],
|
||||
'entity_test_revlog',
|
||||
'view all revisions, delete revision',
|
||||
2,
|
||||
[
|
||||
'@type' => 'entity_test_revlog',
|
||||
'%title' => 'view all revisions, delete revision',
|
||||
'%revision' => '1',
|
||||
],
|
||||
'Revision from Sun, 11 Jan 2009 - 16:00 of Test entity - revisions log view all revisions, delete revision has been deleted.',
|
||||
'/entity_test_revlog/1/revisions',
|
||||
];
|
||||
|
||||
$data['supporting revision log, one revision remaining after delete, view access'] = [
|
||||
[],
|
||||
'entity_test_revlog',
|
||||
'view, view all revisions, delete revision',
|
||||
2,
|
||||
[
|
||||
'@type' => 'entity_test_revlog',
|
||||
'%title' => 'view, view all revisions, delete revision',
|
||||
'%revision' => '3',
|
||||
],
|
||||
'Revision from Sun, 11 Jan 2009 - 16:00 of Test entity - revisions log view, view all revisions, delete revision has been deleted.',
|
||||
'/entity_test_revlog/2/revisions',
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads watchdog entries by channel.
|
||||
*
|
||||
* @param string $channel
|
||||
* The logger channel.
|
||||
*
|
||||
* @return string[]
|
||||
* Watchdog entries.
|
||||
*/
|
||||
protected function getLogs(string $channel): array {
|
||||
return \Drupal::database()->select('watchdog')
|
||||
->fields('watchdog')
|
||||
->condition('type', $channel)
|
||||
->execute()
|
||||
->fetchAll();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,393 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\FunctionalTests\Entity;
|
||||
|
||||
use Drupal\Core\Entity\RevisionLogInterface;
|
||||
use Drupal\entity_test\Entity\EntityTestRev;
|
||||
use Drupal\entity_test\Entity\EntityTestRevPub;
|
||||
use Drupal\entity_test_revlog\Entity\EntityTestWithRevisionLog;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests reverting a revision with revision revert form.
|
||||
*
|
||||
* @group Entity
|
||||
* @group #slow
|
||||
* @coversDefaultClass \Drupal\Core\Entity\Form\RevisionRevertForm
|
||||
*/
|
||||
class RevisionRevertFormTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'block',
|
||||
'entity_test',
|
||||
'entity_test_revlog',
|
||||
'dblog',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->drupalPlaceBlock('page_title_block');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test form revision revert.
|
||||
*/
|
||||
public function testFormRevisionRevert(): void {
|
||||
foreach (self::providerPageTitle() as $page_title) {
|
||||
$this->testPageTitle($page_title[0], $page_title[1]);
|
||||
}
|
||||
$this->testAccessRevertLatestDefault();
|
||||
$this->testAccessRevertLatestForwardRevision();
|
||||
$this->testAccessRevertNonLatest();
|
||||
$this->testPrepareRevision();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests title by whether entity supports revision creation dates.
|
||||
*
|
||||
* @param string $entityTypeId
|
||||
* The entity type to test.
|
||||
* @param string $expectedQuestion
|
||||
* The expected question/page title.
|
||||
*/
|
||||
protected function testPageTitle(string $entityTypeId, string $expectedQuestion): void {
|
||||
/** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
|
||||
$storage = \Drupal::entityTypeManager()->getStorage($entityTypeId);
|
||||
|
||||
$entity = $storage->create([
|
||||
'type' => $entityTypeId,
|
||||
'name' => 'revert',
|
||||
]);
|
||||
if ($entity instanceof RevisionLogInterface) {
|
||||
$date = new \DateTime('11 January 2009 4:00:00pm');
|
||||
$entity->setRevisionCreationTime($date->getTimestamp());
|
||||
}
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
$revisionId = $entity->getRevisionId();
|
||||
|
||||
// Create a new latest revision.
|
||||
if ($entity instanceof RevisionLogInterface) {
|
||||
$entity->setRevisionCreationTime($date->modify('+1 hour')->getTimestamp());
|
||||
}
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
|
||||
// Reload the entity.
|
||||
$revision = $storage->loadRevision($revisionId);
|
||||
$this->drupalGet($revision->toUrl('revision-revert-form'));
|
||||
$this->assertSession()->pageTextContains($expectedQuestion);
|
||||
$this->assertSession()->buttonExists('Revert');
|
||||
$this->assertSession()->linkExists('Cancel');
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testPageTitle.
|
||||
*/
|
||||
protected static function providerPageTitle(): array {
|
||||
return [
|
||||
['entity_test_rev', 'Are you sure you want to revert the revision?'],
|
||||
['entity_test_revlog', 'Are you sure you want to revert to the revision from Sun, 11 Jan 2009 - 16:00?'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test cannot revert latest default revision.
|
||||
*
|
||||
* @covers \Drupal\Core\Entity\EntityAccessControlHandler::checkAccess
|
||||
*/
|
||||
protected function testAccessRevertLatestDefault(): void {
|
||||
/** @var \Drupal\entity_test\Entity\EntityTestRev $entity */
|
||||
$entity = EntityTestRev::create();
|
||||
$entity->setName('revert');
|
||||
$entity->save();
|
||||
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
|
||||
$this->drupalGet($entity->toUrl('revision-revert-form'));
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
$this->assertFalse($entity->access('revert', $this->rootUser, FALSE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that forward revisions can be reverted.
|
||||
*
|
||||
* @covers \Drupal\Core\Entity\EntityAccessControlHandler::checkAccess
|
||||
*/
|
||||
protected function testAccessRevertLatestForwardRevision(): void {
|
||||
/** @var \Drupal\entity_test\Entity\EntityTestRev $entity */
|
||||
$entity = EntityTestRevPub::create();
|
||||
$entity->setName('revert');
|
||||
$entity->isDefaultRevision(TRUE);
|
||||
$entity->setPublished();
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
|
||||
$entity->isDefaultRevision(FALSE);
|
||||
$entity->setUnpublished();
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
|
||||
$this->drupalGet($entity->toUrl('revision-revert-form'));
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertTrue($entity->access('revert', $this->rootUser, FALSE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test can revert non-latest revision.
|
||||
*
|
||||
* @covers \Drupal\Core\Entity\EntityAccessControlHandler::checkAccess
|
||||
*/
|
||||
protected function testAccessRevertNonLatest(): void {
|
||||
/** @var \Drupal\entity_test\Entity\EntityTestRev $entity */
|
||||
$entity = EntityTestRev::create();
|
||||
$entity->setName('revert');
|
||||
$entity->save();
|
||||
$revisionId = $entity->getRevisionId();
|
||||
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
|
||||
// Reload the entity.
|
||||
/** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
|
||||
$storage = \Drupal::entityTypeManager()->getStorage('entity_test_rev');
|
||||
$revision = $storage->loadRevision($revisionId);
|
||||
$this->drupalGet($revision->toUrl('revision-revert-form'));
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertTrue($revision->access('revert', $this->rootUser, FALSE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests revision revert, and expected response after revert.
|
||||
*
|
||||
* @param array $permissions
|
||||
* If not empty, a user will be created and logged in with these
|
||||
* permissions.
|
||||
* @param string $entityTypeId
|
||||
* The entity type to test.
|
||||
* @param string $entityLabel
|
||||
* The entity label, which corresponds to access grants.
|
||||
* @param string $expectedLog
|
||||
* Expected log.
|
||||
* @param string $expectedMessage
|
||||
* Expected messenger message.
|
||||
* @param string $expectedDestination
|
||||
* Expected destination after deletion.
|
||||
*
|
||||
* @covers ::submitForm
|
||||
* @dataProvider providerSubmitForm
|
||||
*/
|
||||
public function testSubmitForm(array $permissions, string $entityTypeId, string $entityLabel, array $expectedLog, string $expectedMessage, string $expectedDestination): void {
|
||||
if (count($permissions) > 0) {
|
||||
$this->drupalLogin($this->createUser($permissions));
|
||||
}
|
||||
/** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
|
||||
$storage = \Drupal::entityTypeManager()->getStorage($entityTypeId);
|
||||
|
||||
$entity = $storage->create([
|
||||
'type' => $entityTypeId,
|
||||
'name' => $entityLabel,
|
||||
]);
|
||||
if ($entity instanceof RevisionLogInterface) {
|
||||
$date = new \DateTime('11 January 2009 4:00:00pm');
|
||||
$entity->setRevisionCreationTime($date->getTimestamp());
|
||||
}
|
||||
$entity->save();
|
||||
$revisionId = $entity->getRevisionId();
|
||||
|
||||
if ($entity instanceof RevisionLogInterface) {
|
||||
$entity->setRevisionCreationTime($date->modify('+1 hour')->getTimestamp());
|
||||
}
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
|
||||
$revision = $storage->loadRevision($revisionId);
|
||||
$this->drupalGet($revision->toUrl('revision-revert-form'));
|
||||
|
||||
$count = $this->countRevisions($entityTypeId);
|
||||
$this->submitForm([], 'Revert');
|
||||
|
||||
// A new revision was created.
|
||||
$this->assertEquals($count + 1, $this->countRevisions($entityTypeId));
|
||||
|
||||
// Destination.
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->addressEquals($expectedDestination);
|
||||
|
||||
// Logger log.
|
||||
$logs = $this->getLogs($entity->getEntityType()->getProvider());
|
||||
$this->assertCount(1, $logs);
|
||||
$this->assertEquals('@type: reverted %title revision %revision.', $logs[0]->message);
|
||||
$this->assertEquals($expectedLog, unserialize($logs[0]->variables));
|
||||
|
||||
// Messenger message.
|
||||
$this->assertSession()->pageTextContains($expectedMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testSubmitForm.
|
||||
*/
|
||||
public static function providerSubmitForm(): array {
|
||||
$data = [];
|
||||
|
||||
$data['not supporting revision log, no version history access'] = [
|
||||
['view test entity'],
|
||||
'entity_test_rev',
|
||||
'view, revert',
|
||||
[
|
||||
'@type' => 'entity_test_rev',
|
||||
'%title' => 'view, revert',
|
||||
'%revision' => '1',
|
||||
],
|
||||
'Entity Test Bundle view, revert has been reverted.',
|
||||
'/entity_test_rev/manage/1',
|
||||
];
|
||||
|
||||
$data['not supporting revision log, version history access'] = [
|
||||
['view test entity'],
|
||||
'entity_test_rev',
|
||||
'view, view all revisions, revert',
|
||||
[
|
||||
'@type' => 'entity_test_rev',
|
||||
'%title' => 'view, view all revisions, revert',
|
||||
'%revision' => '1',
|
||||
],
|
||||
'Entity Test Bundle view, view all revisions, revert has been reverted.',
|
||||
'/entity_test_rev/1/revisions',
|
||||
];
|
||||
|
||||
$data['supporting revision log, no version history access'] = [
|
||||
[],
|
||||
'entity_test_revlog',
|
||||
'view, revert',
|
||||
[
|
||||
'@type' => 'entity_test_revlog',
|
||||
'%title' => 'view, revert',
|
||||
'%revision' => '1',
|
||||
],
|
||||
'Test entity - revisions log view, revert has been reverted to the revision from Sun, 11 Jan 2009 - 16:00.',
|
||||
'/entity_test_revlog/manage/1',
|
||||
];
|
||||
|
||||
$data['supporting revision log, version history access'] = [
|
||||
[],
|
||||
'entity_test_revlog',
|
||||
'view, view all revisions, revert',
|
||||
[
|
||||
'@type' => 'entity_test_revlog',
|
||||
'%title' => 'view, view all revisions, revert',
|
||||
'%revision' => '1',
|
||||
],
|
||||
'Test entity - revisions log view, view all revisions, revert has been reverted to the revision from Sun, 11 Jan 2009 - 16:00.',
|
||||
'/entity_test_revlog/1/revisions',
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the revert process.
|
||||
*
|
||||
* @covers ::prepareRevision
|
||||
*/
|
||||
protected function testPrepareRevision(): void {
|
||||
$user = $this->createUser();
|
||||
$this->drupalLogin($user);
|
||||
|
||||
/** @var \Drupal\entity_test_revlog\Entity\EntityTestWithRevisionLog $entity */
|
||||
$entity = EntityTestWithRevisionLog::create([
|
||||
'type' => 'entity_test_revlog',
|
||||
'name' => 'revert',
|
||||
]);
|
||||
|
||||
$date = new \DateTime('11 January 2009 4:00:00pm');
|
||||
$entity->setRevisionCreationTime($date->getTimestamp());
|
||||
$entity->isDefaultRevision(TRUE);
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
|
||||
$revisionCreationTime = $date->modify('+1 hour')->getTimestamp();
|
||||
$entity->setRevisionCreationTime($revisionCreationTime);
|
||||
$entity->setRevisionUserId(0);
|
||||
$entity->isDefaultRevision(FALSE);
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
$targetRevertRevisionId = $entity->getRevisionId();
|
||||
|
||||
// Create a another revision so the previous revision can be reverted to.
|
||||
$entity->setRevisionCreationTime($date->modify('+1 hour')->getTimestamp());
|
||||
$entity->isDefaultRevision(FALSE);
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
|
||||
$count = $this->countRevisions($entity->getEntityTypeId());
|
||||
|
||||
// Load the revision to be copied.
|
||||
/** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
|
||||
$storage = \Drupal::entityTypeManager()->getStorage($entity->getEntityTypeId());
|
||||
/** @var \Drupal\entity_test_revlog\Entity\EntityTestWithRevisionLog $targetRevision */
|
||||
$targetRevision = $storage->loadRevision($targetRevertRevisionId);
|
||||
|
||||
$this->drupalGet($targetRevision->toUrl('revision-revert-form'));
|
||||
$this->submitForm([], 'Revert');
|
||||
|
||||
// Load the new latest revision.
|
||||
/** @var \Drupal\entity_test_revlog\Entity\EntityTestWithRevisionLog $latestRevision */
|
||||
$latestRevision = $storage->loadUnchanged($entity->id());
|
||||
$this->assertEquals($count + 1, $this->countRevisions($entity->getEntityTypeId()));
|
||||
$this->assertEquals('Copy of the revision from <em class="placeholder">Sun, 11 Jan 2009 - 17:00</em>.', $latestRevision->getRevisionLogMessage());
|
||||
$this->assertGreaterThan($revisionCreationTime, $latestRevision->getRevisionCreationTime());
|
||||
$this->assertEquals($user->id(), $latestRevision->getRevisionUserId());
|
||||
$this->assertTrue($latestRevision->isDefaultRevision());
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads watchdog entries by channel.
|
||||
*
|
||||
* @param string $channel
|
||||
* The logger channel.
|
||||
*
|
||||
* @return string[]
|
||||
* Watchdog entries.
|
||||
*/
|
||||
protected function getLogs(string $channel): array {
|
||||
return \Drupal::database()->select('watchdog')
|
||||
->fields('watchdog')
|
||||
->condition('type', $channel)
|
||||
->execute()
|
||||
->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Count number of revisions for an entity type.
|
||||
*
|
||||
* @param string $entityTypeId
|
||||
* The entity type.
|
||||
*
|
||||
* @return int
|
||||
* Number of revisions for an entity type.
|
||||
*/
|
||||
protected function countRevisions(string $entityTypeId): int {
|
||||
return (int) \Drupal::entityTypeManager()->getStorage($entityTypeId)
|
||||
->getQuery()
|
||||
->accessCheck(FALSE)
|
||||
->allRevisions()
|
||||
->count()
|
||||
->execute();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\FunctionalTests\Entity;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTestRev;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests revision route provider.
|
||||
*
|
||||
* @group Entity
|
||||
* @coversDefaultClass \Drupal\Core\Entity\Routing\RevisionHtmlRouteProvider
|
||||
*/
|
||||
class RevisionRouteProviderTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'block',
|
||||
'entity_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->drupalPlaceBlock('page_title_block');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests title is from revision in context.
|
||||
*/
|
||||
public function testRevisionTitle(): void {
|
||||
$entity = EntityTestRev::create();
|
||||
$entity
|
||||
->setName('first revision, view revision')
|
||||
->setNewRevision();
|
||||
$entity->save();
|
||||
$revisionId = $entity->getRevisionId();
|
||||
|
||||
// A default revision is created to ensure it is not pulled from the
|
||||
// non-revision entity parameter.
|
||||
$entity
|
||||
->setName('second revision, view revision')
|
||||
->setNewRevision();
|
||||
$entity->isDefaultRevision(TRUE);
|
||||
$entity->save();
|
||||
|
||||
// Reload the object.
|
||||
/** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
|
||||
$storage = \Drupal::entityTypeManager()->getStorage('entity_test_rev');
|
||||
$revision = $storage->loadRevision($revisionId);
|
||||
$this->drupalGet($revision->toUrl('revision'));
|
||||
$this->assertSession()->responseContains('first revision');
|
||||
$this->assertSession()->responseNotContains('second revision');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,352 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\FunctionalTests\Entity;
|
||||
|
||||
use Drupal\Core\Entity\Controller\VersionHistoryController;
|
||||
use Drupal\entity_test\Entity\EntityTestRev;
|
||||
use Drupal\entity_test_revlog\Entity\EntityTestWithRevisionLog;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests version history page.
|
||||
*
|
||||
* @group Entity
|
||||
* @group #slow
|
||||
* @coversDefaultClass \Drupal\Core\Entity\Controller\VersionHistoryController
|
||||
*/
|
||||
class RevisionVersionHistoryTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'entity_test',
|
||||
'entity_test_revlog',
|
||||
'user',
|
||||
];
|
||||
|
||||
/**
|
||||
* Test all revisions appear, in order of revision creation.
|
||||
*/
|
||||
public function testOrder(): void {
|
||||
/** @var \Drupal\entity_test_revlog\Entity\EntityTestWithRevisionLog $entity */
|
||||
$entity = EntityTestWithRevisionLog::create(['type' => 'entity_test_revlog']);
|
||||
// Need label to be able to assert order.
|
||||
$entity->setName('view all revisions');
|
||||
$user = $this->drupalCreateUser([], 'first revision');
|
||||
$entity->setRevisionUser($user);
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
|
||||
$entity->setNewRevision();
|
||||
$user = $this->drupalCreateUser([], 'second revision');
|
||||
$entity->setRevisionUser($user);
|
||||
$entity->save();
|
||||
|
||||
$entity->setNewRevision();
|
||||
$user = $this->drupalCreateUser([], 'third revision');
|
||||
$entity->setRevisionUser($user);
|
||||
$entity->save();
|
||||
|
||||
$this->drupalGet($entity->toUrl('version-history'));
|
||||
$this->assertSession()->elementsCount('css', 'table tbody tr', 3);
|
||||
// Order is newest to oldest revision by creation order.
|
||||
$this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(1)', 'third revision');
|
||||
$this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(2)', 'second revision');
|
||||
$this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(3)', 'first revision');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test current revision is indicated.
|
||||
*
|
||||
* @covers \Drupal\Core\Entity\Controller\VersionHistoryController::revisionOverview
|
||||
*/
|
||||
public function testCurrentRevision(): void {
|
||||
/** @var \Drupal\entity_test\Entity\EntityTestRev $entity */
|
||||
$entity = EntityTestRev::create(['type' => 'entity_test_rev']);
|
||||
// Need label to be able to assert order.
|
||||
$entity->setName('view all revisions');
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
|
||||
$this->drupalGet($entity->toUrl('version-history'));
|
||||
$this->assertSession()->elementsCount('css', 'table tbody tr', 3);
|
||||
// Current revision text is found on the latest revision row.
|
||||
$this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(1)', 'Current revision');
|
||||
$this->assertSession()->elementTextNotContains('css', 'table tbody tr:nth-child(2)', 'Current revision');
|
||||
$this->assertSession()->elementTextNotContains('css', 'table tbody tr:nth-child(3)', 'Current revision');
|
||||
// Current revision row has 'revision-current' class.
|
||||
$this->assertSession()->elementAttributeContains('css', 'table tbody tr:nth-child(1)', 'class', 'revision-current');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test description with entity implementing revision log.
|
||||
*
|
||||
* @covers ::getRevisionDescription
|
||||
*/
|
||||
public function testDescriptionRevLog(): void {
|
||||
/** @var \Drupal\entity_test_revlog\Entity\EntityTestWithRevisionLog $entity */
|
||||
$entity = EntityTestWithRevisionLog::create(['type' => 'entity_test_revlog']);
|
||||
$entity->setName('view all revisions');
|
||||
$user = $this->drupalCreateUser([], $this->randomMachineName());
|
||||
$entity->setRevisionUser($user);
|
||||
$entity->setRevisionCreationTime((new \DateTime('2 February 2013 4:00:00pm'))->getTimestamp());
|
||||
$entity->save();
|
||||
|
||||
$this->drupalGet($entity->toUrl('version-history'));
|
||||
$this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(1)', '2 Feb 2013 - 16:00');
|
||||
$this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(1)', $user->getAccountName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test description with entity implementing revision log, with empty values.
|
||||
*
|
||||
* @covers ::getRevisionDescription
|
||||
*/
|
||||
public function testDescriptionRevLogNullValues(): void {
|
||||
$entity = EntityTestWithRevisionLog::create(['type' => 'entity_test_revlog']);
|
||||
$entity->setName('view all revisions')->save();
|
||||
|
||||
// Check entity values are still null after saving; they did not receive
|
||||
// values from currentUser or some other global context.
|
||||
$this->assertNull($entity->getRevisionUser());
|
||||
$this->assertNull($entity->getRevisionUserId());
|
||||
$this->assertNull($entity->getRevisionLogMessage());
|
||||
|
||||
$this->drupalGet($entity->toUrl('version-history'));
|
||||
$this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(1)', 'by Anonymous (not verified)');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test description with entity, without revision log, no label access.
|
||||
*
|
||||
* @covers ::getRevisionDescription
|
||||
*/
|
||||
public function testDescriptionNoRevLogNoLabelAccess(): void {
|
||||
/** @var \Drupal\entity_test\Entity\EntityTestRev $entity */
|
||||
$entity = EntityTestRev::create(['type' => 'entity_test_rev']);
|
||||
$entity->setName('view all revisions');
|
||||
$entity->save();
|
||||
|
||||
$this->drupalGet($entity->toUrl('version-history'));
|
||||
$this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(1)', '- Restricted access -');
|
||||
$this->assertSession()->elementTextNotContains('css', 'table tbody tr:nth-child(1)', $entity->getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test description with entity, without revision log, with label access.
|
||||
*
|
||||
* @covers ::getRevisionDescription
|
||||
*/
|
||||
public function testDescriptionNoRevLogWithLabelAccess(): void {
|
||||
// Permission grants 'view label' access.
|
||||
$this->drupalLogin($this->createUser(['view test entity']));
|
||||
|
||||
/** @var \Drupal\entity_test\Entity\EntityTestRev $entity */
|
||||
$entity = EntityTestRev::create(['type' => 'entity_test_rev']);
|
||||
$entity->setName('view all revisions');
|
||||
$entity->save();
|
||||
|
||||
$this->drupalGet($entity->toUrl('version-history'));
|
||||
$this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(1)', $entity->getName());
|
||||
$this->assertSession()->elementTextNotContains('css', 'table tbody tr:nth-child(1)', '- Restricted access -');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test revision link, without access to revision page.
|
||||
*
|
||||
* @covers ::getRevisionDescription
|
||||
*/
|
||||
public function testDescriptionLinkNoAccess(): void {
|
||||
/** @var \Drupal\entity_test_revlog\Entity\EntityTestWithRevisionLog $entity */
|
||||
$entity = EntityTestWithRevisionLog::create(['type' => 'entity_test_revlog']);
|
||||
$entity->setName('view all revisions');
|
||||
$entity->save();
|
||||
|
||||
$this->drupalGet($entity->toUrl('version-history'));
|
||||
$this->assertSession()->elementsCount('css', 'table tbody tr', 1);
|
||||
$this->assertSession()->elementsCount('css', 'table tbody tr a', 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test revision link, with access to revision page.
|
||||
*
|
||||
* Test two revisions. Usually the latest revision only checks canonical
|
||||
* route access, whereas all others will check individual revision access.
|
||||
*
|
||||
* @covers ::getRevisionDescription
|
||||
*/
|
||||
public function testDescriptionLinkWithAccess(): void {
|
||||
/** @var \Drupal\entity_test_revlog\Entity\EntityTestWithRevisionLog $entity */
|
||||
$entity = EntityTestWithRevisionLog::create(['type' => 'entity_test_revlog']);
|
||||
// Revision has access to individual revision.
|
||||
$entity->setName('view all revisions, view revision');
|
||||
$entity->save();
|
||||
$firstRevisionId = $entity->getRevisionId();
|
||||
|
||||
// Revision has access to canonical route.
|
||||
$entity->setName('view all revisions, view');
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
|
||||
$this->drupalGet($entity->toUrl('version-history'));
|
||||
$row1Link = $this->assertSession()->elementExists('css', 'table tbody tr:nth-child(1) a');
|
||||
$this->assertEquals($entity->toUrl()->toString(), $row1Link->getAttribute('href'));
|
||||
// Reload revision so object has the properties to build a revision link.
|
||||
/** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
|
||||
$storage = \Drupal::entityTypeManager()->getStorage('entity_test_revlog');
|
||||
$firstRevision = $storage->loadRevision($firstRevisionId);
|
||||
$row2Link = $this->assertSession()->elementExists('css', 'table tbody tr:nth-child(2) a');
|
||||
$this->assertEquals($firstRevision->toUrl('revision')->toString(), $row2Link->getAttribute('href'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test revision log message if supported, and HTML tags are stripped.
|
||||
*
|
||||
* @covers ::getRevisionDescription
|
||||
*/
|
||||
public function testDescriptionRevisionLogMessage(): void {
|
||||
/** @var \Drupal\entity_test_revlog\Entity\EntityTestWithRevisionLog $entity */
|
||||
$entity = EntityTestWithRevisionLog::create(['type' => 'entity_test_revlog']);
|
||||
$entity->setName('view all revisions');
|
||||
$entity->setRevisionLogMessage('<em>Hello</em> <script>world</script> <strong>123</strong>');
|
||||
$entity->save();
|
||||
|
||||
$this->drupalGet($entity->toUrl('version-history'));
|
||||
// Script tags are stripped, while admin-safe tags are retained.
|
||||
$this->assertSession()->elementContains('css', 'table tbody tr:nth-child(1)', '<em>Hello</em> world <strong>123</strong>');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test revert operation.
|
||||
*
|
||||
* @covers ::buildRevertRevisionLink
|
||||
*/
|
||||
public function testOperationRevertRevision(): void {
|
||||
/** @var \Drupal\entity_test_revlog\Entity\EntityTestWithRevisionLog $entity */
|
||||
$entity = EntityTestWithRevisionLog::create(['type' => 'entity_test_revlog']);
|
||||
$entity->setName('view all revisions');
|
||||
$entity->save();
|
||||
|
||||
$entity->setName('view all revisions, revert');
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
|
||||
$entity->setName('view all revisions, revert');
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
|
||||
$this->drupalGet($entity->toUrl('version-history'));
|
||||
$this->assertSession()->elementsCount('css', 'table tbody tr', 3);
|
||||
|
||||
// Latest revision does not have revert revision operation: reverting latest
|
||||
// revision is not permitted.
|
||||
$row1 = $this->assertSession()->elementExists('css', 'table tbody tr:nth-child(1)');
|
||||
$this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(1)', 'Current revision');
|
||||
$this->assertSession()->elementNotExists('named', ['link', 'Revert'], $row1);
|
||||
|
||||
// Revision 2 has revert revision operation: granted access.
|
||||
$row2 = $this->assertSession()->elementExists('css', 'table tbody tr:nth-child(2)');
|
||||
$this->assertSession()->elementExists('named', ['link', 'Revert'], $row2);
|
||||
|
||||
// Revision 3 does not have revert revision operation: no access.
|
||||
$row3 = $this->assertSession()->elementExists('css', 'table tbody tr:nth-child(3)');
|
||||
$this->assertSession()->elementNotExists('named', ['link', 'Revert'], $row3);
|
||||
|
||||
$this->drupalGet($entity->toUrl('version-history'));
|
||||
$this->assertSession()->elementsCount('css', 'table tbody tr', 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test delete operation.
|
||||
*
|
||||
* @covers ::buildDeleteRevisionLink
|
||||
*/
|
||||
public function testOperationDeleteRevision(): void {
|
||||
/** @var \Drupal\entity_test_revlog\Entity\EntityTestWithRevisionLog $entity */
|
||||
$entity = EntityTestWithRevisionLog::create(['type' => 'entity_test_revlog']);
|
||||
$entity->setName('view all revisions');
|
||||
$entity->save();
|
||||
|
||||
$entity->setName('view all revisions, delete revision');
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
|
||||
$entity->setName('view all revisions, delete revision');
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
|
||||
$this->drupalGet($entity->toUrl('version-history'));
|
||||
$this->assertSession()->elementsCount('css', 'table tbody tr', 3);
|
||||
|
||||
// Latest revision does not have delete revision operation: deleting latest
|
||||
// revision is not permitted.
|
||||
$row1 = $this->assertSession()->elementExists('css', 'table tbody tr:nth-child(1)');
|
||||
$this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(1)', 'Current revision');
|
||||
$this->assertSession()->elementNotExists('named', ['link', 'Delete'], $row1);
|
||||
|
||||
// Revision 2 has delete revision operation: granted access.
|
||||
$row2 = $this->assertSession()->elementExists('css', 'table tbody tr:nth-child(2)');
|
||||
$this->assertSession()->elementExists('named', ['link', 'Delete'], $row2);
|
||||
|
||||
// Revision 3 does not have delete revision operation: no access.
|
||||
$row3 = $this->assertSession()->elementExists('css', 'table tbody tr:nth-child(3)');
|
||||
$this->assertSession()->elementNotExists('named', ['link', 'Delete'], $row3);
|
||||
$this->drupalGet($entity->toUrl('version-history'));
|
||||
$this->assertSession()->elementsCount('css', 'table tbody tr', 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test revisions are paginated.
|
||||
*/
|
||||
public function testRevisionsPagination(): void {
|
||||
/** @var \Drupal\entity_test\Entity\EntityTestRev $entity */
|
||||
$entity = EntityTestRev::create([
|
||||
'type' => 'entity_test_rev',
|
||||
'name' => 'view all revisions,view revision',
|
||||
]);
|
||||
$entity->save();
|
||||
|
||||
$firstRevisionId = $entity->getRevisionId();
|
||||
|
||||
for ($i = 0; $i < VersionHistoryController::REVISIONS_PER_PAGE; $i++) {
|
||||
$entity->setNewRevision(TRUE);
|
||||
// We need to change something on the entity for it to be considered a new
|
||||
// revision to display. We need "view all revisions" and "view revision"
|
||||
// in a comma separated string to grant access.
|
||||
$entity->setName('view all revisions,view revision,' . $i)->save();
|
||||
}
|
||||
|
||||
$this->drupalGet($entity->toUrl('version-history'));
|
||||
$this->assertSession()->elementsCount('css', 'table tbody tr', VersionHistoryController::REVISIONS_PER_PAGE);
|
||||
$this->assertSession()->elementExists('css', '.pager');
|
||||
|
||||
/** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
|
||||
$storage = $this->container->get('entity_type.manager')->getStorage($entity->getEntityTypeId());
|
||||
$firstRevision = $storage->loadRevision($firstRevisionId);
|
||||
$secondRevision = $storage->loadRevision($firstRevisionId + 1);
|
||||
// We should see everything up to the second revision, but not the first.
|
||||
$this->assertSession()->linkByHrefExists($secondRevision->toUrl('revision')->toString());
|
||||
$this->assertSession()->linkByHrefNotExists($firstRevision->toUrl('revision')->toString());
|
||||
// The next page should show just the first revision.
|
||||
$this->clickLink('Go to next page');
|
||||
$this->assertSession()->elementsCount('css', 'table tbody tr', 1);
|
||||
$this->assertSession()->elementExists('css', '.pager');
|
||||
$this->assertSession()->linkByHrefNotExists($secondRevision->toUrl('revision')->toString());
|
||||
$this->assertSession()->linkByHrefExists($firstRevision->toUrl('revision')->toString());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\FunctionalTests\Entity;
|
||||
|
||||
use Drupal\entity_test_revlog\Entity\EntityTestMulWithRevisionLog;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests version history page with translations.
|
||||
*
|
||||
* @group Entity
|
||||
* @coversDefaultClass \Drupal\Core\Entity\Controller\VersionHistoryController
|
||||
*/
|
||||
final class RevisionVersionHistoryTranslatableTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'entity_test_revlog',
|
||||
'content_translation',
|
||||
'language',
|
||||
'user',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
ConfigurableLanguage::createFromLangcode('es')->save();
|
||||
|
||||
// Rebuild the container so that the new languages are picked up by services
|
||||
// that hold a list of languages.
|
||||
$this->rebuildContainer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the version history page for translations.
|
||||
*/
|
||||
public function testVersionHistoryTranslations(): void {
|
||||
$label = 'view all revisions,revert,delete revision';
|
||||
$entity = EntityTestMulWithRevisionLog::create([
|
||||
'name' => $label,
|
||||
'type' => 'entity_test_mul_revlog',
|
||||
]);
|
||||
$entity->addTranslation('es', ['label' => 'version history test translations es']);
|
||||
$entity->save();
|
||||
|
||||
$firstRevisionId = $entity->getRevisionId();
|
||||
|
||||
$entity->setNewRevision();
|
||||
$entity->setName($label . ',2')
|
||||
->save();
|
||||
|
||||
$this->drupalGet($entity->toUrl('version-history'));
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->elementsCount('css', 'table tbody tr', 2);
|
||||
|
||||
/** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
|
||||
$storage = $this->container->get('entity_type.manager')->getStorage($entity->getEntityTypeId());
|
||||
$firstRevision = $storage->loadRevision($firstRevisionId);
|
||||
|
||||
$this->assertSession()->linkByHrefExists($firstRevision->toUrl('revision-revert-form')->toString());
|
||||
$this->assertSession()->linkByHrefExists($firstRevision->toUrl('revision-delete-form')->toString());
|
||||
$this->assertSession()->linkByHrefNotExists($firstRevision->getTranslation('es')->toUrl('revision-revert-form')->toString());
|
||||
$this->assertSession()->linkByHrefNotExists($firstRevision->getTranslation('es')->toUrl('revision-delete-form')->toString());
|
||||
|
||||
$this->drupalGet($entity->getTranslation('es')->toUrl('version-history'));
|
||||
$this->assertSession()->linkByHrefNotExistsExact($firstRevision->toUrl('revision-revert-form')->toString());
|
||||
$this->assertSession()->linkByHrefNotExistsExact($firstRevision->toUrl('revision-delete-form')->toString());
|
||||
$this->assertSession()->linkByHrefExists($firstRevision->getTranslation('es')->toUrl('revision-revert-form')->toString());
|
||||
$this->assertSession()->linkByHrefExists($firstRevision->getTranslation('es')->toUrl('revision-delete-form')->toString());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\FunctionalTests\Entity;
|
||||
|
||||
use Drupal\Core\Entity\RevisionLogInterface;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests revision view page.
|
||||
*
|
||||
* @group Entity
|
||||
* @coversDefaultClass \Drupal\Core\Entity\Controller\EntityRevisionViewController
|
||||
*/
|
||||
class RevisionViewTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'block',
|
||||
'entity_test',
|
||||
'entity_test_revlog',
|
||||
'field',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->drupalPlaceBlock('page_title_block');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests revision page.
|
||||
*
|
||||
* @param string $entityTypeId
|
||||
* Entity type to test.
|
||||
* @param string $expectedPageTitle
|
||||
* Expected page title.
|
||||
*
|
||||
* @covers ::__invoke
|
||||
*
|
||||
* @dataProvider providerRevisionPage
|
||||
*/
|
||||
public function testRevisionPage(string $entityTypeId, string $expectedPageTitle): void {
|
||||
/** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
|
||||
$storage = \Drupal::entityTypeManager()->getStorage($entityTypeId);
|
||||
|
||||
// Add a field to test revision page output.
|
||||
$fieldStorage = FieldStorageConfig::create([
|
||||
'entity_type' => $entityTypeId,
|
||||
'field_name' => 'foo',
|
||||
'type' => 'string',
|
||||
]);
|
||||
$fieldStorage->save();
|
||||
FieldConfig::create([
|
||||
'field_storage' => $fieldStorage,
|
||||
'bundle' => $entityTypeId,
|
||||
])->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $displayRepository */
|
||||
$displayRepository = \Drupal::service('entity_display.repository');
|
||||
$displayRepository->getViewDisplay($entityTypeId, $entityTypeId)
|
||||
->setComponent('foo', [
|
||||
'type' => 'string',
|
||||
])
|
||||
->save();
|
||||
|
||||
$entity = $storage->create(['type' => $entityTypeId]);
|
||||
$entity->setName('revision 1, view revision');
|
||||
$revision1Body = $this->randomMachineName();
|
||||
$entity->foo = $revision1Body;
|
||||
$entity->setNewRevision();
|
||||
if ($entity instanceof RevisionLogInterface) {
|
||||
$date = new \DateTime('11 January 2009 4:00:00pm');
|
||||
$entity->setRevisionCreationTime($date->getTimestamp());
|
||||
}
|
||||
$entity->save();
|
||||
$revisionId = $entity->getRevisionId();
|
||||
|
||||
$entity->setName('revision 2, view revision');
|
||||
$revision2Body = $this->randomMachineName();
|
||||
$entity->foo = $revision2Body;
|
||||
if ($entity instanceof RevisionLogInterface) {
|
||||
$entity->setRevisionCreationTime($date->modify('+1 hour')->getTimestamp());
|
||||
}
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
|
||||
$revision = $storage->loadRevision($revisionId);
|
||||
$this->drupalGet($revision->toUrl('revision'));
|
||||
|
||||
$this->assertSession()->pageTextContains($expectedPageTitle);
|
||||
$this->assertSession()->pageTextContains($revision1Body);
|
||||
$this->assertSession()->pageTextNotContains($revision2Body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testRevisionPage.
|
||||
*/
|
||||
public static function providerRevisionPage(): array {
|
||||
return [
|
||||
['entity_test_rev', 'Revision of revision 1, view revision'],
|
||||
['entity_test_revlog', 'Revision of revision 1, view revision from Sun, 11 Jan 2009 - 16:00'],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user