diff --git a/core/modules/file/tests/src/Functional/DownloadTest.php b/core/modules/file/tests/src/Functional/DownloadTest.php index b502f90494..79c17a37c6 100644 --- a/core/modules/file/tests/src/Functional/DownloadTest.php +++ b/core/modules/file/tests/src/Functional/DownloadTest.php @@ -176,20 +176,4 @@ private function checkUrl($scheme, $directory, $filename, $expected_url) { $file->delete(); } - /** - * Changes in memory settings. - * - * @param $name - * The name of the setting to return. - * @param $value - * The value of the setting. - * - * @see \Drupal\Core\Site\Settings::get() - */ - protected function settingsSet($name, $value) { - $settings = Settings::getAll(); - $settings[$name] = $value; - new Settings($settings); - } - } diff --git a/core/modules/file/tests/src/Functional/FileFieldDisplayTest.php b/core/modules/file/tests/src/Functional/FileFieldDisplayTest.php index 7042c8a326..6333a3e4c9 100644 --- a/core/modules/file/tests/src/Functional/FileFieldDisplayTest.php +++ b/core/modules/file/tests/src/Functional/FileFieldDisplayTest.php @@ -97,7 +97,7 @@ public function testNodeDisplay() { // Test that fields appear as expected after during the preview. // Add a second file. $name = 'files[' . $field_name . '_1][]'; - $edit_upload[$name] = drupal_realpath($test_file->getFileUri()); + $edit_upload[$name] = \Drupal::service('file_system')->realpath($test_file->getFileUri()); $this->drupalPostForm('node/' . $nid . '/edit', $edit_upload, t('Upload')); // Uncheck the display checkboxes and go to the preview. diff --git a/core/modules/file/tests/src/Functional/FileFieldTestBase.php b/core/modules/file/tests/src/Functional/FileFieldTestBase.php index f7660ff24e..2e5f19e795 100644 --- a/core/modules/file/tests/src/Functional/FileFieldTestBase.php +++ b/core/modules/file/tests/src/Functional/FileFieldTestBase.php @@ -2,8 +2,12 @@ namespace Drupal\Tests\file\Functional; +use Drupal\field\Entity\FieldStorageConfig; +use Drupal\field\Entity\FieldConfig; +use Drupal\file\FileInterface; use Drupal\Tests\BrowserTestBase; -use Drupal\Tests\file\Traits\FileFieldTestBaseTrait; +use Drupal\file\Entity\File; +use Drupal\Tests\TestFileCreationTrait; use Drupal\Tests\Traits\DrupalPostFormWithInvalidOptionsTrait; /** @@ -12,7 +16,9 @@ abstract class FileFieldTestBase extends BrowserTestBase { use DrupalPostFormWithInvalidOptionsTrait; - use FileFieldTestBaseTrait; + use TestFileCreationTrait { + getTestFiles as drupalGetTestFiles; + } /** * Modules to enable. @@ -35,4 +41,296 @@ protected function setUp() { $this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']); } + /** + * Retrieves a sample file of the specified type. + * + * @return \Drupal\file\FileInterface + */ + public function getTestFile($type_name, $size = NULL) { + // Get a file to upload. + $file = current($this->drupalGetTestFiles($type_name, $size)); + + // Add a filesize property to files as would be read by + // \Drupal\file\Entity\File::load(). + $file->filesize = filesize($file->uri); + + return File::create((array) $file); + } + + /** + * Retrieves the fid of the last inserted file. + */ + public function getLastFileId() { + return (int) db_query('SELECT MAX(fid) FROM {file_managed}')->fetchField(); + } + + /** + * Creates a new file field. + * + * @param string $name + * The name of the new field (all lowercase), exclude the "field_" prefix. + * @param string $entity_type + * The entity type. + * @param string $bundle + * The bundle that this field will be added to. + * @param array $storage_settings + * A list of field storage settings that will be added to the defaults. + * @param array $field_settings + * A list of instance settings that will be added to the instance defaults. + * @param array $widget_settings + * A list of widget settings that will be added to the widget defaults. + */ + public function createFileField($name, $entity_type, $bundle, $storage_settings = [], $field_settings = [], $widget_settings = []) { + $field_storage = FieldStorageConfig::create([ + 'entity_type' => $entity_type, + 'field_name' => $name, + 'type' => 'file', + 'settings' => $storage_settings, + 'cardinality' => !empty($storage_settings['cardinality']) ? $storage_settings['cardinality'] : 1, + ]); + $field_storage->save(); + + $this->attachFileField($name, $entity_type, $bundle, $field_settings, $widget_settings); + return $field_storage; + } + + /** + * Attaches a file field to an entity. + * + * @param string $name + * The name of the new field (all lowercase), exclude the "field_" prefix. + * @param string $entity_type + * The entity type this field will be added to. + * @param string $bundle + * The bundle this field will be added to. + * @param array $field_settings + * A list of field settings that will be added to the defaults. + * @param array $widget_settings + * A list of widget settings that will be added to the widget defaults. + */ + public function attachFileField($name, $entity_type, $bundle, $field_settings = [], $widget_settings = []) { + $field = [ + 'field_name' => $name, + 'label' => $name, + 'entity_type' => $entity_type, + 'bundle' => $bundle, + 'required' => !empty($field_settings['required']), + 'settings' => $field_settings, + ]; + FieldConfig::create($field)->save(); + + entity_get_form_display($entity_type, $bundle, 'default') + ->setComponent($name, [ + 'type' => 'file_generic', + 'settings' => $widget_settings, + ]) + ->save(); + // Assign display settings. + entity_get_display($entity_type, $bundle, 'default') + ->setComponent($name, [ + 'label' => 'hidden', + 'type' => 'file_default', + ]) + ->save(); + } + + /** + * Updates an existing file field with new settings. + */ + public function updateFileField($name, $type_name, $field_settings = [], $widget_settings = []) { + $field = FieldConfig::loadByName('node', $type_name, $name); + $field->setSettings(array_merge($field->getSettings(), $field_settings)); + $field->save(); + + entity_get_form_display('node', $type_name, 'default') + ->setComponent($name, [ + 'settings' => $widget_settings, + ]) + ->save(); + } + + /** + * Uploads a file to a node. + * + * @param \Drupal\file\FileInterface $file + * The File to be uploaded. + * @param string $field_name + * The name of the field on which the files should be saved. + * @param $nid_or_type + * A numeric node id to upload files to an existing node, or a string + * indicating the desired bundle for a new node. + * @param bool $new_revision + * The revision number. + * @param array $extras + * Additional values when a new node is created. + * + * @return int + * The node id. + */ + public function uploadNodeFile(FileInterface $file, $field_name, $nid_or_type, $new_revision = TRUE, array $extras = []) { + return $this->uploadNodeFiles([$file], $field_name, $nid_or_type, $new_revision, $extras); + } + + /** + * Uploads multiple files to a node. + * + * @param \Drupal\file\FileInterface[] $files + * The files to be uploaded. + * @param string $field_name + * The name of the field on which the files should be saved. + * @param $nid_or_type + * A numeric node id to upload files to an existing node, or a string + * indicating the desired bundle for a new node. + * @param bool $new_revision + * The revision number. + * @param array $extras + * Additional values when a new node is created. + * @param bool $is_invalid + * FALSE (by default) using drupalPostForm() + * TRUE using drupalPostFormWithInvalidOptions(). + * + * @return int + * The node id. + */ + public function uploadNodeFiles(array $files, $field_name, $nid_or_type, $new_revision = TRUE, array $extras = [], $is_invalid = FALSE) { + $edit = [ + 'title[0][value]' => $this->randomMachineName(), + 'revision' => (string) (int) $new_revision, + ]; + + $node_storage = $this->container->get('entity.manager')->getStorage('node'); + if (is_numeric($nid_or_type)) { + $nid = $nid_or_type; + $node_storage->resetCache([$nid]); + $node = $node_storage->load($nid); + } + else { + // Add a new node. + $extras['type'] = $nid_or_type; + $node = $this->drupalCreateNode($extras); + $nid = $node->id(); + // Save at least one revision to better simulate a real site. + $node->setNewRevision(); + $node->save(); + $node_storage->resetCache([$nid]); + $node = $node_storage->load($nid); + $this->assertNotEqual($nid, $node->getRevisionId(), 'Node revision exists.'); + } + + // Attach files to the node. + $field_storage = FieldStorageConfig::loadByName('node', $field_name); + // File input name depends on number of files already uploaded. + $field_num = count($node->{$field_name}); + $name = 'files[' . $field_name . "_$field_num]"; + if ($field_storage->getCardinality() != 1) { + $name .= '[]'; + } + foreach ($files as $file) { + $file_path = $this->container->get('file_system')->realpath($file->getFileUri()); + if (count($files) == 1) { + $edit[$name] = $file_path; + } + else { + $edit[$name][] = $file_path; + } + } + + if ($is_invalid) { + $this->drupalPostFormWithInvalidOptions("node/$nid/edit", $edit, t('Save')); + } + else { + $this->drupalPostForm("node/$nid/edit", $edit, t('Save')); + } + + return $nid; + } + + /** + * Removes a file from a node. + * + * Note that if replacing a file, it must first be removed then added again. + */ + public function removeNodeFile($nid, $new_revision = TRUE) { + $edit = [ + 'revision' => (string) (int) $new_revision, + ]; + + $this->drupalPostForm('node/' . $nid . '/edit', [], t('Remove')); + $this->drupalPostForm(NULL, $edit, t('Save')); + } + + /** + * Replaces a file within a node. + */ + public function replaceNodeFile($file, $field_name, $nid, $new_revision = TRUE) { + $edit = [ + 'files[' . $field_name . '_0]' => \Drupal::service('file_system')->realpath($file->getFileUri()), + 'revision' => (string) (int) $new_revision, + ]; + + $this->drupalPostForm('node/' . $nid . '/edit', [], t('Remove')); + $this->drupalPostForm(NULL, $edit, t('Save')); + } + + /** + * Asserts that a file exists physically on disk. + * + * Overrides PHPUnit\Framework\Assert::assertFileExists() to also work with + * file entities. + * + * @param \Drupal\File\FileInterface|string $file + * Either the file entity or the file URI. + * @param string $message + * (optional) A message to display with the assertion. + */ + public static function assertFileExists($file, $message = NULL) { + $message = isset($message) ? $message : format_string('File %file exists on the disk.', ['%file' => $file->getFileUri()]); + $filename = $file instanceof FileInterface ? $file->getFileUri() : $file; + parent::assertFileExists($filename, $message); + } + + /** + * Asserts that a file exists in the database. + */ + public function assertFileEntryExists($file, $message = NULL) { + $this->container->get('entity.manager')->getStorage('file')->resetCache(); + $db_file = File::load($file->id()); + $message = isset($message) ? $message : format_string('File %file exists in database at the correct path.', ['%file' => $file->getFileUri()]); + $this->assertEqual($db_file->getFileUri(), $file->getFileUri(), $message); + } + + /** + * Asserts that a file does not exist on disk. + * + * Overrides PHPUnit\Framework\Assert::assertFileExists() to also work with + * file entities. + * + * @param \Drupal\File\FileInterface|string $file + * Either the file entity or the file URI. + * @param string $message + * (optional) A message to display with the assertion. + */ + public static function assertFileNotExists($file, $message = NULL) { + $message = isset($message) ? $message : format_string('File %file exists on the disk.', ['%file' => $file->getFileUri()]); + $filename = $file instanceof FileInterface ? $file->getFileUri() : $file; + parent::assertFileNotExists($filename, $message); + } + + /** + * Asserts that a file does not exist in the database. + */ + public function assertFileEntryNotExists($file, $message) { + $this->container->get('entity.manager')->getStorage('file')->resetCache(); + $message = isset($message) ? $message : format_string('File %file exists in database at the correct path.', ['%file' => $file->getFileUri()]); + $this->assertFalse(File::load($file->id()), $message); + } + + /** + * Asserts that a file's status is set to permanent in the database. + */ + public function assertFileIsPermanent(FileInterface $file, $message = NULL) { + $message = isset($message) ? $message : format_string('File %file is permanent.', ['%file' => $file->getFileUri()]); + $this->assertTrue($file->isPermanent(), $message); + } + } diff --git a/core/modules/file/tests/src/Traits/FileFieldTestBaseTrait.php b/core/modules/file/tests/src/Traits/FileFieldTestBaseTrait.php deleted file mode 100644 index 493ec57019..0000000000 --- a/core/modules/file/tests/src/Traits/FileFieldTestBaseTrait.php +++ /dev/null @@ -1,339 +0,0 @@ -setOwner($user); - } - else { - $file->setOwner($this->adminUser); - } - // Change the file status to be temporary. - $file->setTemporary(); - // Save the changes. - $file->save(); - } - - return $file; - } - - /** - * Retrieves a sample file of the specified type. - * - * @return \Drupal\file\FileInterface - */ - public function getTestFile($type_name, $size = NULL) { - // Get a file to upload. - $file = current($this->drupalGetTestFiles($type_name, $size)); - - // Add a filesize property to files as would be read by - // \Drupal\file\Entity\File::load(). - $file->filesize = filesize($file->uri); - - return File::create((array) $file); - } - - /** - * Retrieves the fid of the last inserted file. - */ - public function getLastFileId() { - return (int) db_query('SELECT MAX(fid) FROM {file_managed}')->fetchField(); - } - - /** - * Creates a new file field. - * - * @param string $name - * The name of the new field (all lowercase), exclude the "field_" prefix. - * @param string $entity_type - * The entity type. - * @param string $bundle - * The bundle that this field will be added to. - * @param array $storage_settings - * A list of field storage settings that will be added to the defaults. - * @param array $field_settings - * A list of instance settings that will be added to the instance defaults. - * @param array $widget_settings - * A list of widget settings that will be added to the widget defaults. - */ - public function createFileField($name, $entity_type, $bundle, $storage_settings = [], $field_settings = [], $widget_settings = []) { - $field_storage = FieldStorageConfig::create([ - 'entity_type' => $entity_type, - 'field_name' => $name, - 'type' => 'file', - 'settings' => $storage_settings, - 'cardinality' => !empty($storage_settings['cardinality']) ? $storage_settings['cardinality'] : 1, - ]); - $field_storage->save(); - - $this->attachFileField($name, $entity_type, $bundle, $field_settings, $widget_settings); - return $field_storage; - } - - /** - * Attaches a file field to an entity. - * - * @param string $name - * The name of the new field (all lowercase), exclude the "field_" prefix. - * @param string $entity_type - * The entity type this field will be added to. - * @param string $bundle - * The bundle this field will be added to. - * @param array $field_settings - * A list of field settings that will be added to the defaults. - * @param array $widget_settings - * A list of widget settings that will be added to the widget defaults. - */ - public function attachFileField($name, $entity_type, $bundle, $field_settings = [], $widget_settings = []) { - $field = [ - 'field_name' => $name, - 'label' => $name, - 'entity_type' => $entity_type, - 'bundle' => $bundle, - 'required' => !empty($field_settings['required']), - 'settings' => $field_settings, - ]; - FieldConfig::create($field)->save(); - - entity_get_form_display($entity_type, $bundle, 'default') - ->setComponent($name, [ - 'type' => 'file_generic', - 'settings' => $widget_settings, - ]) - ->save(); - // Assign display settings. - entity_get_display($entity_type, $bundle, 'default') - ->setComponent($name, [ - 'label' => 'hidden', - 'type' => 'file_default', - ]) - ->save(); - } - - /** - * Updates an existing file field with new settings. - */ - public function updateFileField($name, $type_name, $field_settings = [], $widget_settings = []) { - $field = FieldConfig::loadByName('node', $type_name, $name); - $field->setSettings(array_merge($field->getSettings(), $field_settings)); - $field->save(); - - entity_get_form_display('node', $type_name, 'default') - ->setComponent($name, [ - 'settings' => $widget_settings, - ]) - ->save(); - } - - /** - * Uploads a file to a node. - * - * @param \Drupal\file\FileInterface $file - * The File to be uploaded. - * @param string $field_name - * The name of the field on which the files should be saved. - * @param $nid_or_type - * A numeric node id to upload files to an existing node, or a string - * indicating the desired bundle for a new node. - * @param bool $new_revision - * The revision number. - * @param array $extras - * Additional values when a new node is created. - * - * @return int - * The node id. - */ - public function uploadNodeFile(FileInterface $file, $field_name, $nid_or_type, $new_revision = TRUE, array $extras = []) { - return $this->uploadNodeFiles([$file], $field_name, $nid_or_type, $new_revision, $extras); - } - - /** - * Uploads multiple files to a node. - * - * @param \Drupal\file\FileInterface[] $files - * The files to be uploaded. - * @param string $field_name - * The name of the field on which the files should be saved. - * @param $nid_or_type - * A numeric node id to upload files to an existing node, or a string - * indicating the desired bundle for a new node. - * @param bool $new_revision - * The revision number. - * @param array $extras - * Additional values when a new node is created. - * - * @return int - * The node id. - */ - public function uploadNodeFiles(array $files, $field_name, $nid_or_type, $new_revision = TRUE, array $extras = [], $is_hack = FALSE) { - $edit = [ - 'title[0][value]' => $this->randomMachineName(), - 'revision' => (string) (int) $new_revision, - ]; - - $node_storage = $this->container->get('entity.manager')->getStorage('node'); - if (is_numeric($nid_or_type)) { - $nid = $nid_or_type; - $node_storage->resetCache([$nid]); - $node = $node_storage->load($nid); - } - else { - // Add a new node. - $extras['type'] = $nid_or_type; - $node = $this->drupalCreateNode($extras); - $nid = $node->id(); - // Save at least one revision to better simulate a real site. - $node->setNewRevision(); - $node->save(); - $node_storage->resetCache([$nid]); - $node = $node_storage->load($nid); - $this->assertNotEqual($nid, $node->getRevisionId(), 'Node revision exists.'); - } - - // Attach files to the node. - $field_storage = FieldStorageConfig::loadByName('node', $field_name); - // File input name depends on number of files already uploaded. - $field_num = count($node->{$field_name}); - $name = 'files[' . $field_name . "_$field_num]"; - if ($field_storage->getCardinality() != 1) { - $name .= '[]'; - } - foreach ($files as $file) { - $file_path = $this->container->get('file_system')->realpath($file->getFileUri()); - if (count($files) == 1) { - $edit[$name] = $file_path; - } - else { - $edit[$name][] = $file_path; - } - } - if ($is_hack) { - $this->drupalPostFormWithInvalidOptions("node/$nid/edit", $edit, t('Save')); - } - else { - $this->drupalPostForm("node/$nid/edit", $edit, t('Save')); - } - - return $nid; - } - - /** - * Removes a file from a node. - * - * Note that if replacing a file, it must first be removed then added again. - */ - public function removeNodeFile($nid, $new_revision = TRUE) { - $edit = [ - 'revision' => (string) (int) $new_revision, - ]; - - $this->drupalPostForm('node/' . $nid . '/edit', [], t('Remove')); - $this->drupalPostForm(NULL, $edit, t('Save')); - } - - /** - * Replaces a file within a node. - */ - public function replaceNodeFile($file, $field_name, $nid, $new_revision = TRUE) { - $edit = [ - 'files[' . $field_name . '_0]' => \Drupal::service('file_system')->realpath($file->getFileUri()), - 'revision' => (string) (int) $new_revision, - ]; - - $this->drupalPostForm('node/' . $nid . '/edit', [], t('Remove')); - $this->drupalPostForm(NULL, $edit, t('Save')); - } - - /** - * Asserts that a file exists physically on disk. - * - * Overrides PHPUnit\Framework\Assert::assertFileExists() to also work with - * file entities. - * - * @param \Drupal\File\FileInterface|string $file - * Either the file entity or the file URI. - * @param string $message - * (optional) A message to display with the assertion. - */ - public static function assertFileExists($file, $message = NULL) { - $message = isset($message) ? $message : format_string('File %file exists on the disk.', ['%file' => $file->getFileUri()]); - $filename = $file instanceof FileInterface ? $file->getFileUri() : $file; - parent::assertFileExists($filename, $message); - } - - /** - * Asserts that a file exists in the database. - */ - public function assertFileEntryExists($file, $message = NULL) { - $this->container->get('entity.manager')->getStorage('file')->resetCache(); - $db_file = File::load($file->id()); - $message = isset($message) ? $message : format_string('File %file exists in database at the correct path.', ['%file' => $file->getFileUri()]); - $this->assertEqual($db_file->getFileUri(), $file->getFileUri(), $message); - } - - /** - * Asserts that a file does not exist on disk. - * - * Overrides PHPUnit\Framework\Assert::assertFileExists() to also work with - * file entities. - * - * @param \Drupal\File\FileInterface|string $file - * Either the file entity or the file URI. - * @param string $message - * (optional) A message to display with the assertion. - */ - public static function assertFileNotExists($file, $message = NULL) { - $message = isset($message) ? $message : format_string('File %file exists on the disk.', ['%file' => $file->getFileUri()]); - $filename = $file instanceof FileInterface ? $file->getFileUri() : $file; - parent::assertFileNotExists($filename, $message); - } - - /** - * Asserts that a file does not exist in the database. - */ - public function assertFileEntryNotExists($file, $message) { - $this->container->get('entity.manager')->getStorage('file')->resetCache(); - $message = isset($message) ? $message : format_string('File %file exists in database at the correct path.', ['%file' => $file->getFileUri()]); - $this->assertFalse(File::load($file->id()), $message); - } - - /** - * Asserts that a file's status is set to permanent in the database. - */ - public function assertFileIsPermanent(FileInterface $file, $message = NULL) { - $message = isset($message) ? $message : format_string('File %file is permanent.', ['%file' => $file->getFileUri()]); - $this->assertTrue($file->isPermanent(), $message); - } - -} diff --git a/core/tests/Drupal/Tests/DrupalPostFormWithInvalidOptionsTraitTest.php b/core/tests/Drupal/FunctionalTests/DrupalPostFormWithInvalidOptionsTraitTest.php similarity index 60% rename from core/tests/Drupal/Tests/DrupalPostFormWithInvalidOptionsTraitTest.php rename to core/tests/Drupal/FunctionalTests/DrupalPostFormWithInvalidOptionsTraitTest.php index 9752ddac74..a0f298cbf2 100644 --- a/core/tests/Drupal/Tests/DrupalPostFormWithInvalidOptionsTraitTest.php +++ b/core/tests/Drupal/FunctionalTests/DrupalPostFormWithInvalidOptionsTraitTest.php @@ -1,20 +1,21 @@ drupalPostFormWithInvalidOptions('node/1/edit', $edit, NULL, [], [], $form_html_id); $this->assertSession()->pageTextContains('Page Test by ID has been updated'); - // Check that raw arguments have more prior than $edit. + // Check that raw arguments have more priority than $edit. $edit = ['title[0][value]' => 'Test2']; - $raw_edit = ['form_params' => ['title' => [['value' => 'Test3']]]]; - $this->drupalPostFormWithInvalidOptions('node/1/edit', $edit, t('Save'), [], [], NULL, $raw_edit); + $params = ['title' => [['value' => 'Test3']]]; + $this->drupalPostFormWithInvalidOptions('node/1/edit', $edit, t('Save'), [], [], NULL, $params); $this->assertSession()->pageTextContains('Page Test3 has been updated'); } /** - * Tests post form request with non existent field. + * Tests post form requests with non-existent fields. */ public function testNonExistentField() { - // Check that edit params can contains values of nonexistent fields. + // Check that edit params can contain values of non-existent fields. $edit = [ 'title[0][value]' => 'Chell', 'cake' => 'is a lie', @@ -121,7 +122,7 @@ public function testNonExistentField() { $this->drupalPostFormWithInvalidOptions('node/1/edit', $edit, t('Save')); $this->assertSession()->pageTextContains('Page Chell has been updated'); - // Check that drupalPostForm() can not do this request. + // Check that drupalPostForm() cannot do this request. try { $this->drupalPostForm('node/1/edit', $edit, t('Save')); $this->fail('drupalPostForm() does not work with nonexistent field'); @@ -132,15 +133,15 @@ public function testNonExistentField() { } /** - * Tests post form request with hidden field. + * Tests post form requests with hidden fields. */ public function testHiddenField() { - // Check post request with change hidden field. + // Check post request with changed hidden field. $edit = ['changed' => 1234567890]; $this->drupalPostFormWithInvalidOptions('node/1/edit', $edit, t('Save')); $this->assertSession()->pageTextContains('The content has either been modified by another user, or you have already submitted modifications. As a result, your changes cannot be saved.'); - // Check that drupalPostForm() can not do this request. + // Check that drupalPostForm() cannot do this request. try { $this->drupalPostForm('node/1/edit', $edit, t('Save')); $this->fail('drupalPostForm() does not work with hidden field'); @@ -151,11 +152,11 @@ public function testHiddenField() { } /** - * Tests post form request with invalid field. + * Tests post form requests with invalid fields. */ public function testInvalidFileField() { - $file = current($this->getTestFiles('text')); - $path = \Drupal::service('file_system')->realpath($file->uri); + $file = $this->getTestFile('text'); + $path = \Drupal::service('file_system')->realpath($file->getFileUri()); // Check post request with invalid options. $edit = [ @@ -170,7 +171,7 @@ public function testInvalidFileField() { $this->drupalPostFormWithInvalidOptions('file-test/save_upload_from_form_test', $edit, t('Submit')); $this->assertSession()->pageTextContains('Epic upload FAIL!'); - // Check that drupalPostForm() can not do this request. + // Check that drupalPostForm() cannot do this request. try { $this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit')); $this->fail('drupalPostForm() does not work with array instead of string'); @@ -181,20 +182,20 @@ public function testInvalidFileField() { } /** - * Tests post form request with multi file field. + * Tests post form requests with multi file fields. */ public function testMultiFileFieldWithLimit() { $this->createFileField('field_file_multi', 'node', 'page', ['cardinality' => 2]); - $file = current($this->getTestFiles('text')); - $path = \Drupal::service('file_system')->realpath($file->uri); + $file = $this->getTestFile('text'); + $path = \Drupal::service('file_system')->realpath($file->getFileUri()); - // Check post request with more files, that limit of field. + // Check post request with more files than the limit set on the field. $edit = ['files[field_file_multi_0][]' => [$path, $path, $path]]; $this->drupalPostFormWithInvalidOptions('node/1/edit', $edit, t('Save')); $this->assertSession()->pageTextContains('Field field_file_multi can only hold 2 values but there were 3 uploaded.'); - // Check that drupalPostForm() can not do this request. + // Check that drupalPostForm() cannot do this request. try { $this->drupalPostForm('node/1/edit', $edit, t('Save')); $this->fail('drupalPostForm() does not work with more files that cardinality of field'); @@ -204,4 +205,63 @@ public function testMultiFileFieldWithLimit() { } } + /** + * Tests post form requests when a field already contains a file. + */ + public function testAddValuesForFieldWithExistValues() { + $field_storage_settings = [ + 'display_field' => '1', + 'display_default' => '0', + 'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED, + ]; + $field_settings = [ + 'description_field' => '1', + ]; + $this->createFileField('field_file_multi', 'node', 'page', $field_storage_settings, $field_settings); + // Add 1 file to field. + $test_file = $this->getTestFile('text'); + $test_file_path = \Drupal::service('file_system')->realpath($test_file->getFileUri()); + $this->uploadNodeFile($test_file, 'field_file_multi', 1); + + $assert = $this->assertSession(); + + // Edit first value. + $edit['field_file_multi[0][description]'] = 'description0'; + $edit['field_file_multi[0][display]'] = '1'; + // Add second value. + $edit['files[field_file_multi_1][]'] = $test_file_path; + $edit['field_file_multi[1][description]'] = 'description1'; + $edit['field_file_multi[1][display]'] = '1'; + + // Test 'Preview' request without pre-save values. + $this->drupalPostFormWithInvalidOptions('node/1/edit', $edit, t('Preview')); + $assert->linkExistsExact('description0'); + $assert->linkExistsExact('description1'); + + $this->clickLink(t('Back to content editing')); + + // Ensure that existing field values are changed. + $this->assertSame('description0', $assert->fieldExists('field_file_multi[0][description]')->getValue()); + $this->assertSame('1', $assert->fieldExists('field_file_multi[0][display]')->getAttribute('value')); + // Ensure that non-existing field values are lost. + $assert->fieldNotExists('field_file_multi[1][description]'); + $assert->fieldNotExists('field_file_multi[1][display]'); + + // Test 'Upload' request. + $this->drupalPostFormWithInvalidOptions(NULL, $edit, t('Upload')); + $this->assertSame('description0', $assert->fieldExists('field_file_multi[0][description]')->getValue()); + $this->assertSame('description1', $assert->fieldExists('field_file_multi[1][description]')->getValue()); + + // Test 'Preview' request with pre-save values (after upload new file). + $edit['field_file_multi[0][description]'] = 'description0-1'; + $edit['field_file_multi[1][description]'] = 'description1-1'; + $this->drupalPostFormWithInvalidOptions(NULL, $edit, t('Preview')); + $assert->linkExistsExact('description0-1'); + $assert->linkExistsExact('description1-1'); + $this->clickLink(t('Back to content editing')); + // Ensure that both values are changed. + $this->assertSame('description0-1', $assert->fieldExists('field_file_multi[0][description]')->getValue()); + $this->assertSame('description1-1', $assert->fieldExists('field_file_multi[1][description]')->getValue()); + } + } diff --git a/core/tests/Drupal/Tests/Traits/DrupalPostFormWithInvalidOptionsTrait.php b/core/tests/Drupal/Tests/Traits/DrupalPostFormWithInvalidOptionsTrait.php index 9c9e3a32ce..a4339154b3 100644 --- a/core/tests/Drupal/Tests/Traits/DrupalPostFormWithInvalidOptionsTrait.php +++ b/core/tests/Drupal/Tests/Traits/DrupalPostFormWithInvalidOptionsTrait.php @@ -3,21 +3,29 @@ namespace Drupal\Tests\Traits; /** - * Provides API for send form with invalid options via POST request. + * Provides API for sending a form with invalid options via POST request. * * This trait is meant to be used only by test classes of BrowserTestBase. * - * The goal is to simplify as much as possible the collection of additional data - * required for post form. Such as session, values of other fields/properties of - * the form, the correct setting non-simple fields (e.g. multi files). + * Mink doesn't allow the setting of invalid data in a form, or the modification + * of hidden field. This trait can be used when these types of situations need + * to be tested. + * + * See more examples in \Drupal\Tests\DrupalPostFormWithInvalidOptionsTraitTest. */ trait DrupalPostFormWithInvalidOptionsTrait { /** - * Executes POST request to form with unverified data. + * Executes POST request to form with invalid options. * * This method can be used when it is not appropriate to use drupalPostForm(). * + * This can be used when needing to test: + * - changing hidden fields. + * - adding values without fields. + * - setting invalid values by type data or limit. + * - sending a form with special headers. + * * @param \Drupal\Core\Url|string $path * (optional) Location of the post form. * Will be used current path if NULL value is set. @@ -26,29 +34,83 @@ * Use this parameter to describe the values of existing fields in the form. * @param string|object $submit * (optional) Value of the submit button whose click is to be emulated. + * By default, the first button with submit type will be used. * Example: 'Save', t('Save'). * @param array $options * (optional) Options to be forwarded to the url generator. * @param array $headers * (optional) An array containing additional HTTP request headers, each - * formatted as "name: value". + * formatted as "name: value". It will be used as $server parameter in + * \Symfony\Component\BrowserKit::request(). * @param string $form_html_id - * (optional) HTML ID of the form to be submitted. - * @param array $raw_edit - * (optional) A array of additional data to append to the POST request. - * Use this parameter for values that must be applied without processing. - * Values will be taken from two keys 'form_multipart' and 'form_params': + * (optional) HTML ID of the form to be submitted. It is necessary, when the + * $submit parameter is not specified, or on the page there are several + * corresponding forms. + * @param array $raw_params + * @param array $raw_files + * (optional) Additional data to append to the request. + * It will be transferred to \Symfony\Component\BrowserKit::request() as is. + * Wherein: + * 'form_params' -> $parameters + * 'form_multipart' -> $files * Example: * [ * 'form_params' => ['title' => [['value' => 'Test']]], * 'form_multipart' => [['files[field]' => 'path']], * ]; - * This is used when $edit values can not be correct divided. Like case when - * it is need to edit values via multipart group, but the form does not have - * file fields with such names, or need test several values for single file - * fields. + * Use this to avoid processing, such as $edit parameter. */ - protected function drupalPostFormWithInvalidOptions($path = NULL, array $edit = [], $submit = NULL, array $options = [], array $headers = [], $form_html_id = NULL, $raw_edit = []) { + protected function drupalPostFormWithInvalidOptions($path = NULL, array $edit = [], $submit = NULL, array $options = [], array $headers = [], $form_html_id = NULL, $raw_params = [], $raw_files=[]) { + $form = $this->getCrawlerForm($path, $options, $submit, $form_html_id); + $form_params = $form->getPhpValues(); + $form_files = $form->getPhpFiles(); + + $edit_params = $this->prepareEditValues($edit); + $edit_files = []; + if (isset($edit_params['files'])) { + $edit_files['files'] = $edit_params['files']; + unset($edit_params['files']); + } + + $params = array_replace_recursive($form_params, $edit_params, $raw_params); + $files = array_replace_recursive($form_files, $edit_files, $raw_files); + + $method = $form->getMethod() ?: 'POST'; + $action = $form->getUri() ?: $this->getUrl(); + + $this->drupalRequest($method, $action, $params, $files, $headers); + } + + /** + * Perform a request with update browser page. + * + * For send the form use: + * drupalPostForm() with valid data + * drupalPostFormWithInvalidOptions() with invalid data + * For navigation use drupalGet(). + * For more flexible request use http client. + */ + protected function drupalRequest($method, $action, $params = [], $multipart = [], $headers = []) { + $this->prepareRequest(); + $client = $this->getSession()->getDriver()->getClient(); + $client->request($method, $action, $params, $multipart, $headers); + $this->refreshVariables(); + // Check if there are any meta refresh redirects (like Batch API pages). + if ($this->checkForMetaRefresh()) { + // We are finished with all meta refresh redirects. + $this->metaRefreshCount = 0; + } + } + + /** + * Gets the crawler form by html ID or button name on the page. + * + * @return \Symfony\Component\DomCrawler\Form + * Crawler form. + * + * @throws \Behat\Mink\Exception\ElementNotFoundException + */ + protected function getCrawlerForm($path = NULL, $options = NULL, $submit = NULL, $form_html_id = NULL) { if ($path !== NULL) { $this->drupalGet($path, $options); } @@ -59,102 +121,71 @@ protected function drupalPostFormWithInvalidOptions($path = NULL, array $edit = /** @var \Drupal\Tests\WebAssert $assert */ $assert = $this->assertSession(); - - // Get form and submit button. - if ($form_html_id !== NULL || $submit === NULL) { - $form_selector = '//form' . ($form_html_id ? "[@id='$form_html_id']" : ''); - $form_element = $assert->elementExists('xpath', $form_selector); + if ($form_html_id !== NULL) { + $form_element = $assert->elementExists('xpath', "//form[@id='$form_html_id']"); $submit_button = ($submit !== NULL) ? $form_element->findField($submit) : $form_element->find('css', '[type="submit"]'); } - else { + elseif ($submit !== NULL) { $submit_button = $assert->buttonExists($submit); $form_element = $assert->elementExists('xpath', './ancestor::form', $submit_button); } + else { + $form_element = $assert->elementExists('xpath', '//form'); + $submit_button = $form_element->find('css', '[type="submit"]'); + } - // Get form values. $element = $submit_button ?: $form_element; $crawler = $this->getSession()->getDriver()->getClient()->getCrawler(); $form = $crawler->filterXPath($element->getXpath())->form(); - $form_params = $form->getPhpValues(); - $form_multipart = $form->getPhpFiles(); - // Get edit values. - $edit = $this->castSafeStrings($edit); - // Decouple multipart values (files) from other params. - $edit_params = []; - $edit_multipart = []; - foreach ($edit as $name => $value) { - $field = $form_element->findField($name); - if ($field && $field->getAttribute('type') === 'file') { - $edit_multipart[$name] = $value; - } - else { - $edit_params[$name] = $value; - } - } - $edit_multipart = $this->prepareFilesValue($edit_multipart); - $edit_params = $this->getPhpValues($edit_params); - $edit_multipart = $this->getPhpValues($edit_multipart); - - // Get raw edit values. - $raw_edit_params = isset($raw_edit['form_params']) ? $raw_edit['form_params'] : []; - $raw_edit_multipart = isset($raw_edit['multipart']) ? $raw_edit['multipart'] : []; - - // Combine form, edit and raw values. - $params = array_replace_recursive($form_params, $edit_params, $raw_edit_params); - $multipart = array_replace_recursive($form_multipart, $edit_multipart, $raw_edit_multipart); - - $this->prepareRequest(); - - $client = $this->getSession()->getDriver()->getClient(); - $method = $form_element->getAttribute('method') ?: 'POST'; - $action = $form_element->getAttribute('action') ?: $this->getUrl(); - $client->request($method, $action, $params, $multipart, $headers); - - $this->refreshVariables(); + return $form; + } - // Check if there are any meta refresh redirects (like Batch API pages). - if ($this->checkForMetaRefresh()) { - // We are finished with all meta refresh redirects. - $this->metaRefreshCount = 0; - } + /** + * Prepare edit values to request. + * + * @return array + * An array with prepared values. + */ + protected function prepareEditValues(array $edit = []) { + $edit = $this->castSafeStrings($edit); + $edit = $this->convertMultipleValues($edit); + $edit = $this->getPhpValues($edit); + return $edit; } /** - * Expands the list of file values, and replaces paths on absolute path. + * Convert item with array values to items with string values. * * Example: * [ - * 'files[field_single]' => 'path0', - * 'files[field_multi][]' => ['path1', 'path2'] + * 'key1' => 'value1', + * 'key2[]' => ['value2', 'value3'] * ] * will be converted to * [ - * 'files[field_single]' => 'real/path0', - * 'files[field_multi][0]' => 'real/path1', - * 'files[field_multi][1]' => 'real/path2', + * 'key1' => 'value1', + * 'key2[0]' => 'value2', + * 'key2[1]' => 'value3', * ] * - * @param array $files + * @param array $values * List of files to prepare, each item as 'field_name' => value. * * @return array - * Prepared array of files. + * Prepared array of values after converting. */ - protected function prepareFilesValue($files) { - foreach ($files as $key => $paths) { - if (is_array($paths)) { - foreach ($paths as $i => $path) { + protected function convertMultipleValues(array $values) { + foreach ($values as $key => $item) { + if (is_array($item)) { + foreach ($item as $i => $value) { $new_key = str_replace('[]', "[$i]", $key); - $files[$new_key] = $path; + $values[$new_key] = $value; } - unset($files[$key]); + unset($values[$key]); } } - foreach ($files as $key => $path) { - $files[$key] = $this->container->get('file_system')->realpath($path); - } - return $files; + return $values; } /** @@ -164,22 +195,33 @@ protected function prepareFilesValue($files) { * * This method converts fields with the array notation. * Example: - * ['foo[0][bar]' => 'value'] to ['foo' => [['bar' => 'value']]. + * [ + * 'foo[0][bar]' => 'value', + * ] + * will be converted to + * [ + * 'foo' => [ + * [ + * 'bar' => 'value', + * ], + * ], + * ] + * + * @param array $post + * An array of field values with the string notation. * * @return array - * An array of field values. + * An array of field values with the array notation. */ - public function getPhpValues($post) { - $values = array(); + protected function getPhpValues($post) { + $values = []; foreach ($post as $name => $value) { - $qs = http_build_query(array($name => $value), '', '&'); + $qs = http_build_query([$name => $value], '', '&'); if (!empty($qs)) { parse_str($qs, $expandedValue); - $varName = substr($name, 0, strlen(key($expandedValue))); - $values = array_replace_recursive($values, array($varName => current($expandedValue))); + $values = array_replace_recursive($values, $expandedValue); } } - return $values; }