first commit

This commit is contained in:
2024-07-15 12:33:27 +02:00
commit ce50ae282b
22084 changed files with 2623791 additions and 0 deletions

View File

@@ -0,0 +1,143 @@
<?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');
$storage->resetCache([$entity->id()]);
$entity = $storage->load($entity->id());
$this->assertEquals([
['shape' => 'rectangle', 'color' => 'green'],
], $entity->get($this->fieldName)->getValue());
}
}

View File

@@ -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.');
}
}

View File

@@ -0,0 +1,166 @@
<?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;
/**
* Modules to enable.
*
* @var array
*/
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);
}
}

View File

@@ -0,0 +1,82 @@
<?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;
/**
* Modules to enable.
*
* @var array
*/
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');
}
}

View File

@@ -0,0 +1,343 @@
<?php
declare(strict_types=1);
namespace Drupal\FunctionalTests\Entity;
use Drupal\Component\Render\FormattableMarkup;
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.
*
* @param string $entityTypeId
* The entity type to test.
* @param string $expectedQuestion
* The expected question/page title.
*
* @covers ::getQuestion
* @dataProvider providerPageTitle
*/
public function testPageTitle(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, 01/11/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);
}
/**
* Ensure that forward revision can be deleted.
*
* @covers \Drupal\Core\Entity\EntityAccessControlHandler::checkAccess
*/
public 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
*/
public 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
*/
public 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, 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
* @dataProvider providerSubmitForm
*/
public function testSubmitForm(array $permissions, string $entityTypeId, string $entityLabel, int $totalRevisions, string $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->assertEquals([0 => $expectedLog], $logs);
// Messenger message.
$this->assertSession()->pageTextContains($expectedMessage);
}
/**
* 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,
'entity_test_rev: deleted <em class="placeholder">view all revisions, delete revision</em> revision <em class="placeholder">1</em>.',
'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,
'entity_test_rev: deleted <em class="placeholder">view, view all revisions, delete revision</em> revision <em class="placeholder">1</em>.',
'Revision of Entity Test Bundle view, view all revisions, delete revision has been deleted.',
'/entity_test_rev/1/revisions',
];
$data['supporting revision log, one revision remaining after delete, no view access'] = [
[],
'entity_test_revlog',
'view all revisions, delete revision',
2,
'entity_test_revlog: deleted <em class="placeholder">view all revisions, delete revision</em> revision <em class="placeholder">1</em>.',
'Revision from Sun, 01/11/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,
'entity_test_revlog: deleted <em class="placeholder">view, view all revisions, delete revision</em> revision <em class="placeholder">1</em>.',
'Revision from Sun, 01/11/2009 - 16:00 of Test entity - revisions log view, view all revisions, delete revision has been deleted.',
'/entity_test_revlog/1/revisions',
];
return $data;
}
/**
* Loads watchdog entries by channel.
*
* @param string $channel
* The logger channel.
*
* @return string[]
* Watchdog entries.
*/
protected function getLogs(string $channel): array {
$logs = \Drupal::database()->query("SELECT * FROM {watchdog} WHERE type = :type", [':type' => $channel])->fetchAll();
return array_map(function (object $log) {
return (string) new FormattableMarkup($log->message, unserialize($log->variables));
}, $logs);
}
}

View File

@@ -0,0 +1,364 @@
<?php
declare(strict_types=1);
namespace Drupal\FunctionalTests\Entity;
use Drupal\Component\Render\FormattableMarkup;
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');
}
/**
* 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
* @dataProvider providerPageTitle
*/
public 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.
*/
public 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, 01/11/2009 - 16:00?'],
];
}
/**
* Test cannot revert latest default revision.
*
* @covers \Drupal\Core\Entity\EntityAccessControlHandler::checkAccess
*/
public 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
*/
public 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
*/
public 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, string $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->assertEquals([0 => $expectedLog], $logs);
// 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',
'entity_test_rev: reverted <em class="placeholder">view, revert</em> revision <em class="placeholder">1</em>.',
'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',
'entity_test_rev: reverted <em class="placeholder">view, view all revisions, revert</em> revision <em class="placeholder">1</em>.',
'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',
'entity_test_revlog: reverted <em class="placeholder">view, revert</em> revision <em class="placeholder">1</em>.',
'Test entity - revisions log view, revert has been reverted to the revision from Sun, 01/11/2009 - 16:00.',
'/entity_test_revlog/manage/1',
];
$data['supporting revision log, version history access'] = [
[],
'entity_test_revlog',
'view, view all revisions, revert',
'entity_test_revlog: reverted <em class="placeholder">view, view all revisions, revert</em> revision <em class="placeholder">1</em>.',
'Test entity - revisions log view, view all revisions, revert has been reverted to the revision from Sun, 01/11/2009 - 16:00.',
'/entity_test_revlog/1/revisions',
];
return $data;
}
/**
* Tests the revert process.
*
* @covers ::prepareRevision
*/
public 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, 01/11/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 {
$logs = \Drupal::database()->query("SELECT * FROM {watchdog} WHERE type = :type", [':type' => $channel])->fetchAll();
return array_map(function (object $log) {
return (string) new FormattableMarkup($log->message, unserialize($log->variables));
}, $logs);
}
/**
* 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();
}
}

View File

@@ -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');
}
}

View File

@@ -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)', '02/02/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());
}
}

View File

@@ -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());
}
}

View File

@@ -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, 01/11/2009 - 16:00'],
];
}
}