View source
<?php
namespace Drupal\Tests\autosave_form\FunctionalJavascript\ContentEntity;
use Drupal\autosave_form\Storage\AutosaveEntityFormStorageInterface;
use Drupal\Component\Utility\Html;
use Drupal\Core\Entity\EntityChangedInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\Tests\autosave_form\FunctionalJavascript\AutosaveFormTestBase;
use Drupal\user\EntityOwnerInterface;
abstract class ContentEntityAutosaveFormTestBase extends AutosaveFormTestBase {
protected $entityType;
protected $bundle;
protected $originalEntityTitle;
protected $changedEntityTitle;
protected $unlimitedCardinalityField = 'autosave_unlimited_field';
protected $requiredField = 'autosave_required_field';
protected $testAutosaveFormExistingEntityChangesCount = 5;
protected function prepareSetUp() {
$this
->createMultipleTestField();
$this
->createRequiredTestField();
parent::prepareSetUp();
$this->originalEntityTitle = NULL;
$this->changedEntityTitle = NULL;
}
public function testAutosaveForms() {
$this
->doTestAutosaveFormNewEntity();
$this
->relogUser();
$this
->doTestAutosaveFormExistingEntity();
$this
->relogUser();
$this
->doTestSavingRestoredEntityForm();
$this
->relogUser();
$this
->doTestConcurrentEditing();
$this
->relogUser();
$this
->doTestAutosaveAfterFormValidationFail();
$this
->relogUser();
$this
->doTestAutosaveStatesPurgingOnConfigEvent();
}
protected function doTestAutosaveFormNewEntity() {
$this
->drupalGet($this
->getCreateNewEntityURL());
$this
->assertAutosaveFormLibraryLoaded(FALSE);
}
protected function doTestAutosaveFormExistingEntity() {
$entity = $this
->createTestEntity();
$entity_id = $entity
->id();
$entity_form_edit_url = $entity
->toUrl('edit-form');
$this
->drupalGet($entity_form_edit_url);
$this
->assertAutosaveFormLibraryLoaded(TRUE);
$this
->assertOriginalEntityTitleAsPageTitle();
$this
->assertTrue($this
->waitForAutosaveSubmits(2));
$this
->assertEquals(0, $this
->getCountAutosaveEntries($entity_id));
$latest_autosave_timestamp_per_change = $this
->makeAllEntityFormChanges($entity_id);
for ($change_id = $this->testAutosaveFormExistingEntityChangesCount; $change_id > 0; $change_id--) {
$this
->drupalGet($entity_form_edit_url);
if (($last_autosave_timestamp = $this
->getLastAutosaveTimestamp($entity_id)) && $last_autosave_timestamp > $latest_autosave_timestamp_per_change[$change_id]) {
$delete_timestamps = range($latest_autosave_timestamp_per_change[$change_id] + 1, $last_autosave_timestamp);
$this
->deleteAutosavedStates($delete_timestamps);
}
$this
->reloadPageAndRestore($entity_form_edit_url, $this
->getLastAutosaveTimestamp($entity_id));
$this
->assertCorrectlyRestoredEntityFormState($change_id);
}
}
protected function doTestSavingRestoredEntityForm() {
$entity = $this
->createTestEntity();
$entity_form_edit_url = $entity
->toUrl('edit-form');
$this
->drupalGet($entity_form_edit_url);
$this
->makeAllEntityFormChanges($entity
->id());
$this
->assertTrue($this
->waitForAutosaveSubmits(1));
$this
->reloadPageAndRestore($entity_form_edit_url, $this
->getLastAutosaveTimestamp($entity
->id()));
$this
->saveForm();
$this
->finalizeTestSavingRestoredEntityForm($entity
->id());
}
protected function saveForm() {
$this
->drupalPostForm(NULL, [], t('Save'));
}
protected function doTestConcurrentEditing() {
$entity = $this
->createTestEntity();
if (!$entity instanceof EntityChangedInterface) {
return;
}
$entity_form_edit_url = $entity
->toUrl('edit-form');
$this
->drupalGet($entity_form_edit_url);
$this
->assertTrue($this
->waitForAutosaveSubmits(1));
$this
->makeEntityFormChange(1);
$this
->assertTrue($this
->waitForAutosaveSubmits(2));
$this
->assertTrue($this
->getCountAutosaveEntries($entity
->id()) > 0);
$this
->assertAutosaveIsRunning(TRUE);
$entity
->setChangedTime($entity
->getChangedTime() + 1)
->save();
$this
->assertFalse($this
->waitForAutosaveSubmits(2));
$this
->assertAutosaveIsRunning(FALSE);
$this
->assertEquals(0, $this
->getCountAutosaveEntries($entity
->id()));
$message = $this
->config('autosave_form.messages')
->get('entity_saved_in_background_alert_message');
$this
->assertSession()
->responseContains($message);
}
protected function doTestAutosaveAfterFormValidationFail() {
$entity = $this
->createTestEntity();
\Drupal::state()
->set('disable_html5_validation', TRUE);
$entity_form_edit_url = $entity
->toUrl('edit-form');
$this
->drupalGet($entity_form_edit_url);
$this
->assertTrue($this
->waitForAutosaveSubmits(1));
$this
->alterTitleField();
$this
->emptyRequiredFieldTestAutosaveAfterFormValidationFail();
$this
->assertTrue($this
->waitForAutosaveSubmits(2));
$before_submission_autosave_entries = $this
->getCountAutosaveEntries($entity
->id());
$this
->assertTrue($before_submission_autosave_entries > 0);
$this
->saveForm();
\Drupal::state()
->delete('disable_html5_validation');
$this
->logHtmlOutput(__FUNCTION__ . ' after validation fail.');
$error_messages = $this
->getSession()
->getPage()
->find('css', '.messages--error');
$this
->assertNotNull($error_messages);
$this
->assertAutosaveResumeDiscardMessageIsShown(FALSE, $this
->getLastAutosaveTimestamp($entity
->id()));
$this
->assertTrue($this
->waitForAutosaveSubmits(2));
$this
->assertEquals($before_submission_autosave_entries, $this
->getCountAutosaveEntries($entity
->id()));
}
protected function doTestAutosaveStatesPurgingOnConfigEvent() {
$entity = $this
->createTestEntity();
$entity_id = $entity
->id();
$entity_form_edit_url = $entity
->toUrl('edit-form');
$create_autosave_state = function () use ($entity_form_edit_url, $entity_id) {
$this
->drupalGet($entity_form_edit_url);
$this
->assertTrue($this
->waitForAutosaveSubmits(2));
$this
->makeEntityFormChange(1);
$this
->assertTrue($this
->waitForAutosaveSubmits(2));
$this
->assertEquals(1, $this
->getCountAutosaveEntries($entity_id));
};
$create_autosave_state();
$field_config = FieldConfig::loadByName($this->entityType, $this->bundle, $this->unlimitedCardinalityField);
$field_config
->setLabel('New Label Test AutosaveState Purge')
->save();
$this
->assertEquals(1, $this
->getCountAutosaveEntries($entity_id));
$field_storage = FieldStorageConfig::loadByName($this->entityType, $this->unlimitedCardinalityField);
$field_storage
->setCardinality(10)
->save();
$this
->assertEquals(0, $this
->getCountAutosaveEntries($entity_id));
$create_autosave_state();
$field_storage = FieldStorageConfig::loadByName($this->entityType, $this->unlimitedCardinalityField);
$field_storage
->delete();
$this
->assertEquals(0, $this
->getCountAutosaveEntries($entity_id));
$create_autosave_state();
$this
->createMultipleTestField();
$this
->assertEquals(0, $this
->getCountAutosaveEntries($entity_id));
}
protected function emptyRequiredFieldTestAutosaveAfterFormValidationFail() {
$this
->fillTestField($this->requiredField, 0, '');
}
protected function finalizeTestSavingRestoredEntityForm($entity_id) {
$entity = $this
->reloadEntity($entity_id);
$this
->assertEquals($entity
->label(), $this->changedEntityTitle);
$this
->assertEquals(2, $entity
->get($this->unlimitedCardinalityField)
->count());
$this
->assertEquals('delta 0', $entity
->get($this->unlimitedCardinalityField)
->get(0)->value);
$this
->assertEquals('delta 1', $entity
->get($this->unlimitedCardinalityField)
->get(1)->value);
$this
->assertEquals('required test field', $entity
->get($this->requiredField)
->get(0)->value);
}
protected function makeAllEntityFormChanges($entity_id) {
$this
->logHtmlOutput(__FUNCTION__ . ' before changes are made');
$this
->assertTrue($this
->waitForAutosaveSubmits(1));
$latest_autosave_timestamp_per_change = [];
for ($change_id = 1; $change_id <= $this->testAutosaveFormExistingEntityChangesCount; $change_id++) {
$before_change_autosave_entries = $this
->getCountAutosaveEntries($entity_id);
$this
->makeEntityFormChange($change_id);
$this
->assertTrue($this
->waitForAutosaveSubmits(2));
$after_change_autosave_entries = $this
->getCountAutosaveEntries($entity_id);
$this
->assertTrue($after_change_autosave_entries > $before_change_autosave_entries);
$this
->assertTrue($this
->waitForAutosaveSubmits(2));
$this
->assertEquals($after_change_autosave_entries, $this
->getCountAutosaveEntries($entity_id));
$latest_autosave_timestamp_per_change[$change_id] = $this
->getLastAutosaveTimestamp($entity_id);
}
$this
->logHtmlOutput(__FUNCTION__ . ' after changes are made');
return $latest_autosave_timestamp_per_change;
}
protected function makeEntityFormChange($change_id) {
$this
->logHtmlOutput(__FUNCTION__ . ' before change ' . $change_id);
switch ($change_id) {
case 1:
$this
->alterTitleField();
break;
case 2:
$this
->fillTestField($this->unlimitedCardinalityField, 0, 'delta 0');
break;
case 3:
$new_delta_expected = 1;
$this
->addItemToUnlimitedTestField($new_delta_expected);
break;
case 4:
$field_delta = 1;
$this
->fillTestField($this->unlimitedCardinalityField, $field_delta, 'delta 1');
break;
case 5:
$this
->fillTestField($this->requiredField, 0, 'required test field');
break;
}
$this
->logHtmlOutput(__FUNCTION__ . ' after change ' . $change_id);
}
protected function assertCorrectlyRestoredEntityFormState($change_id) {
$page = $this
->getSession()
->getPage();
$this
->logHtmlOutput(__FUNCTION__ . ' before restore of change ' . $change_id);
switch ($change_id) {
case 5:
$test_field_delta_0 = $page
->findField($this->requiredField . '[0][value]');
$this
->assertNotEmpty($test_field_delta_0);
$this
->assertEquals('required test field', $test_field_delta_0
->getValue());
case 4:
$test_field_delta_1 = $page
->findField($this->unlimitedCardinalityField . '[1][value]');
$this
->assertNotEmpty($test_field_delta_1);
$this
->assertEquals('delta 1', $test_field_delta_1
->getValue());
case 3:
if ($change_id == 3) {
$test_field_delta_1 = $page
->findField($this->unlimitedCardinalityField . '[1][value]');
$this
->assertNotEmpty($test_field_delta_1);
$this
->assertEquals('', $test_field_delta_1
->getValue());
}
case 2:
$test_field_delta_0 = $page
->findField($this->unlimitedCardinalityField . '[0][value]');
$this
->assertNotEmpty($test_field_delta_0);
$this
->assertEquals('delta 0', $test_field_delta_0
->getValue());
case 1:
$this
->assertOriginalEntityTitleAsPageTitle();
$entity_type = \Drupal::entityTypeManager()
->getDefinition($this->entityType);
$this
->assertEquals($this->changedEntityTitle, $page
->findField($entity_type
->getKey('label') . '[0][value]')
->getValue());
break;
}
$this
->logHtmlOutput(__FUNCTION__ . ' after restore of change ' . $change_id);
}
protected function alterTitleField($changed_title = 'changed title') {
if ($label_field_name = \Drupal::entityTypeManager()
->getDefinition($this->entityType)
->getKey('label')) {
$this->changedEntityTitle = $changed_title;
$this
->getSession()
->getPage()
->fillField($label_field_name . '[0][value]', $this->changedEntityTitle);
}
}
protected function fillTestField($field_name, $delta, $value) {
$this
->getSession()
->getPage()
->fillField("{$field_name}[{$delta}][value]", $value);
}
protected function addItemToUnlimitedTestField($delta) {
$this
->logHtmlOutput(__FUNCTION__ . ' before adding a new item to unlimited field');
$page = $this
->getSession()
->getPage();
$add_button = $page
->find('css', '[data-drupal-selector="edit-' . Html::cleanCssIdentifier($this->unlimitedCardinalityField) . '-add-more"]');
$this
->assertTrue(!empty($add_button));
$add_button
->press();
$result = $this
->assertSession()
->waitForElement('css', "[name=\"{$this->unlimitedCardinalityField}[{$delta}][value]\"]");
$this
->assertNotEmpty($result);
$this
->logHtmlOutput(__FUNCTION__ . ' after adding a new item to unlimited field');
}
protected function getCurrentPageTitle() {
$element = $this
->getSession()
->getPage()
->find('css', '.page-title');
$title = !empty($element) ? $element
->getText() : NULL;
return $title;
}
protected function assertOriginalEntityTitleAsPageTitle() {
$current_title = $this
->getCurrentPageTitle();
$this
->assertTrue(strpos($current_title, $this->originalEntityTitle) !== FALSE);
}
protected function getCountAutosaveEntries($entity_id) {
$count = \Drupal::database()
->select(AutosaveEntityFormStorageInterface::AUTOSAVE_ENTITY_FORM_TABLE)
->condition('entity_id', $entity_id)
->countQuery()
->execute()
->fetchField();
return $count !== FALSE ? $count : 0;
}
protected function getCountAutosaveSessionEntries() {
$count = \Drupal::database()
->select(AutosaveEntityFormStorageInterface::AUTOSAVE_ENTITY_FORM_TABLE)
->groupBy('form_session_id')
->countQuery()
->execute()
->fetchField();
return $count !== FALSE ? $count : 0;
}
protected function getLastAutosaveTimestamp($entity_id) {
$timestamp = \Drupal::database()
->select(AutosaveEntityFormStorageInterface::AUTOSAVE_ENTITY_FORM_TABLE, 't')
->fields('t', [
'timestamp',
])
->condition('entity_id', $entity_id)
->orderBy('timestamp', 'DESC')
->execute()
->fetchField();
return $timestamp !== FALSE ? $timestamp : NULL;
}
protected function deleteAutosavedStates(array $timestamps = NULL) {
$query = \Drupal::database()
->delete(AutosaveEntityFormStorageInterface::AUTOSAVE_ENTITY_FORM_TABLE);
if (isset($timestamps)) {
$query
->condition('timestamp', $timestamps, 'IN');
}
$query
->execute();
}
protected function createTestEntity() {
$entity_type_manager = \Drupal::entityTypeManager();
$entity_type = $entity_type_manager
->getDefinition($this->entityType);
$storage = $entity_type_manager
->getStorage($this->entityType);
$values = [
'type' => $this->bundle,
];
if ($label_field_name = $entity_type
->getKey('label')) {
$values[$label_field_name] = 'original title';
}
$entity = $storage
->create($values);
if ($entity instanceof EntityOwnerInterface) {
$entity
->setOwner($this->webUser);
}
elseif ($entity_type
->hasKey('uid')) {
$entity
->set('uid', $this->webUser
->id());
}
$entity
->save();
$this->originalEntityTitle = $entity
->label();
return $entity;
}
protected function reloadEntity($id) {
$storage = \Drupal::entityTypeManager()
->getStorage($this->entityType);
$storage
->resetCache([
$id,
]);
return $storage
->load($id);
}
protected function createMultipleTestField() {
if (!FieldStorageConfig::loadByName($this->entityType, $this->unlimitedCardinalityField)) {
FieldStorageConfig::create([
'field_name' => $this->unlimitedCardinalityField,
'entity_type' => $this->entityType,
'type' => 'text',
'cardinality' => -1,
])
->save();
}
if (!FieldConfig::loadByName($this->entityType, $this->bundle, $this->unlimitedCardinalityField)) {
FieldConfig::create([
'field_name' => $this->unlimitedCardinalityField,
'entity_type' => $this->entityType,
'bundle' => $this->bundle,
'label' => $this
->randomMachineName() . '_label',
])
->save();
$this
->getEntityFormDisplay($this->entityType, $this->bundle, 'default')
->setComponent($this->unlimitedCardinalityField, [
'type' => 'text_textfield',
])
->save();
}
}
protected function createRequiredTestField() {
if (!FieldStorageConfig::loadByName($this->entityType, $this->requiredField)) {
FieldStorageConfig::create([
'field_name' => $this->requiredField,
'entity_type' => $this->entityType,
'type' => 'text',
'cardinality' => 1,
])
->save();
}
if (!FieldConfig::loadByName($this->entityType, $this->bundle, $this->requiredField)) {
FieldConfig::create([
'field_name' => $this->requiredField,
'entity_type' => $this->entityType,
'bundle' => $this->bundle,
'label' => $this->requiredField,
'required' => TRUE,
])
->save();
$this
->getEntityFormDisplay($this->entityType, $this->bundle, 'default')
->setComponent($this->requiredField, [
'type' => 'text_textfield',
])
->save();
}
}
protected function relogUser() {
$this
->drupalLogout();
$this
->drupalLogin($this->webUser);
}
protected abstract function getCreateNewEntityURL();
private function getEntityFormDisplay($entity_type, $bundle, $form_mode) {
if (version_compare(\Drupal::VERSION, '8.8', '>=')) {
return \Drupal::service('entity_display.repository')
->getFormDisplay($entity_type, $bundle, $form_mode);
}
else {
$function = 'entity_get_form_display';
return $function($entity_type, $bundle, $form_mode);
}
}
}