reverted: --- b/core/modules/image/src/Tests/QuickEditImageTest.php +++ /dev/null @@ -1,138 +0,0 @@ -createRole(['access in-place editing']); - $this->adminUser->addRole($rid); - $this->adminUser->save(); - - // Create a field with basic resolution validators. - $field_settings = [ - 'max_resolution' => '100x', - 'min_resolution' => '50x', - ]; - $this->createImageField('image_test', 'article', [], $field_settings); - } - - /** - * Tests that the field info route returns expected data. - */ - function testFieldInfo() { - // Create a test Node. - $node = $this->drupalCreateNode([ - 'type' => 'article', - 'title' => t('Test Node'), - ]); - $info = $this->drupalGetJSON('quickedit/image/info/node/' . $node->id() . '/image_test/' . $node->language()->getId() . '/default'); - // Assert that the default settings for our field are respected by our JSON - // endpoint. - $this->assertTrue($info['alt_field']); - $this->assertFalse($info['title_field']); - } - - /** - * Tests that uploading a valid image works. - */ - function testValidImageUpload() { - // Create a test Node. - $node = $this->drupalCreateNode([ - 'type' => 'article', - 'title' => t('Test Node'), - ]); - - // We want a test image that is a valid size. - $valid_image = FALSE; - $image_factory = $this->container->get('image.factory'); - foreach ($this->drupalGetTestFiles('image') as $image) { - $image_file = $image_factory->get($image->uri); - if ($image_file->getWidth() > 50 && $image_file->getWidth() < 100) { - $valid_image = $image; - break; - } - } - $this->uploadImage($valid_image, $node->id(), 'image_test', $node->language()->getId()); - $this->assertText('fid', t('Valid upload completed successfully.')); - } - - /** - * Tests that uploading a invalid image does not work. - */ - function testInvalidUpload() { - // Create a test Node. - $node = $this->drupalCreateNode([ - 'type' => 'article', - 'title' => t('Test Node'), - ]); - - // We want a test image that will fail validation. - $invalid_image = FALSE; - /** @var \Drupal\Core\Image\ImageFactory $image_factory */ - $image_factory = $this->container->get('image.factory'); - foreach ($this->drupalGetTestFiles('image') as $image) { - /** @var \Drupal\Core\Image\ImageInterface $image_file */ - $image_file = $image_factory->get($image->uri); - if ($image_file->getWidth() < 50 || $image_file->getWidth() > 100 ) { - $invalid_image = $image; - break; - } - } - $this->uploadImage($invalid_image, $node->id(), 'image_test', $node->language()->getId()); - $this->assertText('main_error', t('Invalid upload returned errors.')); - } - - /** - * Uploads an image using the image module's Quick Edit route. - * - * @param object $image - * The image to upload. - * @param integer $nid - * The target node ID. - * @param string $field_name - * The target field machine name. - * @param string $langcode - * The langcode to use when setting the field's value. - * - * @return mixed - * The content returned from the call to $this->curlExec(). - */ - function uploadImage($image, $nid, $field_name, $langcode) { - $filepath = $this->container->get('file_system')->realpath($image->uri); - $data = [ - 'files[image]' => curl_file_create($filepath), - ]; - $path = 'quickedit/image/upload/node/' . $nid . '/' . $field_name . '/' . $langcode . '/default'; - // We assemble the curl request ourselves as drupalPost cannot process file - // uploads, and drupalPostForm only works with typical Drupal forms. - return $this->curlExec([ - CURLOPT_URL => $this->buildUrl($path, []), - CURLOPT_POST => TRUE, - CURLOPT_POSTFIELDS => $data, - CURLOPT_HTTPHEADER => [ - 'Accept: application/json', - 'Content-Type: multipart/form-data', - ], - ]); - } - -} only in patch2: unchanged: --- /dev/null +++ b/core/modules/image/src/Tests/ImageFieldCreationTrait.php @@ -0,0 +1,68 @@ + $name, + 'entity_type' => 'node', + 'type' => 'image', + 'settings' => $storage_settings, + 'cardinality' => !empty($storage_settings['cardinality']) ? $storage_settings['cardinality'] : 1, + ))->save(); + + $field_config = FieldConfig::create([ + 'field_name' => $name, + 'label' => $name, + 'entity_type' => 'node', + 'bundle' => $type_name, + 'required' => !empty($field_settings['required']), + 'settings' => $field_settings, + 'description' => $description, + ]); + $field_config->save(); + + entity_get_form_display('node', $type_name, 'default') + ->setComponent($name, array( + 'type' => 'image_image', + 'settings' => $widget_settings, + )) + ->save(); + + entity_get_display('node', $type_name, 'default') + ->setComponent($name, array( + 'type' => 'image', + 'settings' => $formatter_settings, + )) + ->save(); + + return $field_config; + } + +} only in patch2: unchanged: --- a/core/modules/image/src/Tests/ImageFieldTestBase.php +++ b/core/modules/image/src/Tests/ImageFieldTestBase.php @@ -2,9 +2,7 @@ namespace Drupal\image\Tests; -use Drupal\field\Entity\FieldConfig; use Drupal\simpletest\WebTestBase; -use Drupal\field\Entity\FieldStorageConfig; /** * TODO: Test the following functions. @@ -24,6 +22,8 @@ */ abstract class ImageFieldTestBase extends WebTestBase { + use ImageFieldCreationTrait; + /** * Modules to enable. * @@ -52,62 +52,6 @@ protected function setUp() { } /** - * Create a new image field. - * - * @param string $name - * The name of the new field (all lowercase), exclude the "field_" prefix. - * @param string $type_name - * The node type 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 - * Widget settings to be added to the widget defaults. - * @param array $formatter_settings - * Formatter settings to be added to the formatter defaults. - * @param string $description - * A description for the field. - */ - function createImageField($name, $type_name, $storage_settings = array(), $field_settings = array(), $widget_settings = array(), $formatter_settings = array(), $description = '') { - FieldStorageConfig::create(array( - 'field_name' => $name, - 'entity_type' => 'node', - 'type' => 'image', - 'settings' => $storage_settings, - 'cardinality' => !empty($storage_settings['cardinality']) ? $storage_settings['cardinality'] : 1, - ))->save(); - - $field_config = FieldConfig::create([ - 'field_name' => $name, - 'label' => $name, - 'entity_type' => 'node', - 'bundle' => $type_name, - 'required' => !empty($field_settings['required']), - 'settings' => $field_settings, - 'description' => $description, - ]); - $field_config->save(); - - entity_get_form_display('node', $type_name, 'default') - ->setComponent($name, array( - 'type' => 'image_image', - 'settings' => $widget_settings, - )) - ->save(); - - entity_get_display('node', $type_name, 'default') - ->setComponent($name, array( - 'type' => 'image', - 'settings' => $formatter_settings, - )) - ->save(); - - return $field_config; - - } - - /** * Preview an image in a node. * * @param \Drupal\Core\Image\ImageInterface $image only in patch2: unchanged: --- /dev/null +++ b/core/modules/image/src/Tests/QuickEditImageControllerTest.php @@ -0,0 +1,146 @@ +createRole(['access in-place editing']); + $this->adminUser->addRole($rid); + $this->adminUser->save(); + + // Create a field with basic resolution validators. + $this->fieldName = strtolower($this->randomMachineName()); + $field_settings = [ + 'max_resolution' => '100x', + 'min_resolution' => '50x', + ]; + $this->createImageField($this->fieldName, 'article', [], $field_settings); + } + + /** + * Tests that the field info route returns expected data. + */ + function testFieldInfo() { + // Create a test Node. + $node = $this->drupalCreateNode([ + 'type' => 'article', + 'title' => t('Test Node'), + ]); + $info = $this->drupalGetJSON('quickedit/image/info/node/' . $node->id() . '/' . $this->fieldName . '/' . $node->language()->getId() . '/default'); + // Assert that the default settings for our field are respected by our JSON + // endpoint. + $this->assertTrue($info['alt_field']); + $this->assertFalse($info['title_field']); + } + + /** + * Tests that uploading a valid image works. + */ + function testValidImageUpload() { + // Create a test Node. + $node = $this->drupalCreateNode([ + 'type' => 'article', + 'title' => t('Test Node'), + ]); + + // We want a test image that is a valid size. + $valid_image = FALSE; + $image_factory = $this->container->get('image.factory'); + foreach ($this->drupalGetTestFiles('image') as $image) { + $image_file = $image_factory->get($image->uri); + if ($image_file->getWidth() > 50 && $image_file->getWidth() < 100) { + $valid_image = $image; + break; + } + } + $this->assertTrue($valid_image); + $this->uploadImage($valid_image, $node->id(), $this->fieldName, $node->language()->getId()); + $this->assertText('fid', t('Valid upload completed successfully.')); + } + + /** + * Tests that uploading a invalid image does not work. + */ + function testInvalidUpload() { + // Create a test Node. + $node = $this->drupalCreateNode([ + 'type' => 'article', + 'title' => t('Test Node'), + ]); + + // We want a test image that will fail validation. + $invalid_image = FALSE; + /** @var \Drupal\Core\Image\ImageFactory $image_factory */ + $image_factory = $this->container->get('image.factory'); + foreach ($this->drupalGetTestFiles('image') as $image) { + /** @var \Drupal\Core\Image\ImageInterface $image_file */ + $image_file = $image_factory->get($image->uri); + if ($image_file->getWidth() < 50 || $image_file->getWidth() > 100 ) { + $invalid_image = $image; + break; + } + } + $this->assertTrue($invalid_image); + $this->uploadImage($invalid_image, $node->id(), $this->fieldName, $node->language()->getId()); + $this->assertText('main_error', t('Invalid upload returned errors.')); + } + + /** + * Uploads an image using the image module's Quick Edit route. + * + * @param object $image + * The image to upload. + * @param integer $nid + * The target node ID. + * @param string $field_name + * The target field machine name. + * @param string $langcode + * The langcode to use when setting the field's value. + * + * @return mixed + * The content returned from the call to $this->curlExec(). + */ + function uploadImage($image, $nid, $field_name, $langcode) { + $filepath = $this->container->get('file_system')->realpath($image->uri); + $data = [ + 'files[image]' => curl_file_create($filepath), + ]; + $path = 'quickedit/image/upload/node/' . $nid . '/' . $field_name . '/' . $langcode . '/default'; + // We assemble the curl request ourselves as drupalPost cannot process file + // uploads, and drupalPostForm only works with typical Drupal forms. + return $this->curlExec([ + CURLOPT_URL => $this->buildUrl($path, []), + CURLOPT_POST => TRUE, + CURLOPT_POSTFIELDS => $data, + CURLOPT_HTTPHEADER => [ + 'Accept: application/json', + 'Content-Type: multipart/form-data', + ], + ]); + } + +} only in patch2: unchanged: --- /dev/null +++ b/core/modules/image/tests/src/FunctionalJavascript/QuickEditImageTest.php @@ -0,0 +1,149 @@ +profile != 'standard') { + $this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']); + } + + $admin = $this->drupalCreateUser(['access contextual links', 'access in-place editing', 'access content', 'access administration pages', 'administer site configuration', 'administer content types', 'administer node fields', 'administer nodes', 'create article content', 'edit any article content', 'delete any article content', 'administer node display']); + $this->drupalLogin($admin); + + // Create a field with a basic filetype restriction. + $field_name = strtolower($this->randomMachineName()); + $field_settings = [ + 'file_extensions' => 'png', + ]; + $formatter_settings = [ + 'image_style' => 'large', + 'image_link' => '', + ]; + $this->createImageField($field_name, 'article', [], $field_settings, [], $formatter_settings); + + // Find images that match our field settings. + $valid_images = []; + foreach ($this->getTestFiles('image') as $image) { + // This regex is taken from file_validate_extensions(). + $regex = '/\.(' . preg_replace('/ +/', '|', preg_quote($field_settings['file_extensions'])) . ')$/i'; + if (preg_match($regex, $image->filename)) { + $valid_images[] = $image; + } + } + + // Ensure we have at least two valid images. + $this->assertGreaterThanOrEqual(2, count($valid_images)); + + // Create a File entity for the initial image. + $file = File::create([ + 'uri' => $valid_images[0]->uri, + 'uid' => $admin->id(), + 'status' => FILE_STATUS_PERMANENT, + ]); + $file->save(); + + // Use the first valid image to create a new Node. + $image_factory = $this->container->get('image.factory'); + $image = $image_factory->get($valid_images[0]->uri); + $node = $this->drupalCreateNode([ + 'type' => 'article', + 'title' => t('Test Node'), + $field_name => [ + 'target_id' => $file->id(), + 'alt' => 'Hello world', + 'title' => '', + 'width' => $image->getWidth(), + 'height' => $image->getHeight(), + ], + ]); + + // Visit the new Node. + $this->drupalGet('node/' . $node->id()); + + // Assert that the initial image is present. + $this->assertSession()->elementExists('css', 'img[src*="' . $valid_images[0]->filename . '"]'); + + $entity_selector = '[data-quickedit-entity-id="node/' . $node->id() . '"]'; + $field_selector = '[data-quickedit-field-id="node/' . $node->id() . '/' . $field_name . '/' . $node->language()->getId() . '/full"]'; + + // Wait until Quick Edit loads. + $condition = "jQuery('" . $entity_selector . " .quickedit').length > 0"; + $this->assertJsCondition($condition, 10000); + + // Initiate Quick Editing. + $this->triggerClick($entity_selector . ' [data-contextual-id] > button'); + $this->click($entity_selector . ' [data-contextual-id] .quickedit > a'); + $this->click($field_selector); + + // Check that our Dropzone element exists. + $this->assertSession()->elementExists('css', $field_selector . ' .quickedit-image-dropzone'); + + // Our headless browser can't drag+drop files, but we can mock the event. + // Append a hidden upload element to the DOM. + $script = 'jQuery("").appendTo("body")'; + $this->getSession()->executeScript($script); + + // Find the element, and set its value to our new image. + $input = $this->assertSession()->elementExists('css', '#quickedit-image-test-input'); + $filepath = $this->container->get('file_system')->realpath($valid_images[1]->uri); + $input->attachFile($filepath); + + // Trigger the upload logic with a mock "drop" event. + $script = 'var e = jQuery.Event("drop");' + . 'e.originalEvent = {dataTransfer: {files: jQuery("#quickedit-image-test-input").get(0).files}};' + . 'e.preventDefault = e.stopPropagation = function() {};' + . 'jQuery(".quickedit-image-dropzone").trigger(e);'; + $this->getSession()->executeScript($script); + + // Wait for AJAX to finish. + $this->assertSession()->assertWaitOnAjaxRequest(); + + // Save the change. + $this->click('.quickedit-button.action-save'); + $this->assertSession()->assertWaitOnAjaxRequest(); + + // Re-visit the page to make sure the edit worked. + $this->drupalGet('node/' . $node->id()); + $this->assertSession()->elementNotExists('css', 'img[src*="' . $valid_images[0]->filename . '"]'); + $this->assertSession()->elementExists('css', 'img[src*="' . $valid_images[1]->filename . '"]'); + } + + /** + * Clicks the element with the given CSS selector using event triggers. + * + * @todo Remove when https://github.com/jcalderonzumba/gastonjs/issues/19 + * is fixed. Currently clicking anchors/buttons with nested elements is not + * possible. + * + * @param string $css_selector + * The CSS selector identifying the element to click. + */ + protected function triggerClick($css_selector) { + $this->getSession()->executeScript("jQuery('" . $css_selector . "')[0].click()"); + } + +} only in patch2: unchanged: --- /dev/null +++ b/core/modules/simpletest/src/TestFilesTrait.php @@ -0,0 +1,117 @@ +generatedTestFiles)) { + // Generate binary test files. + $lines = array(64, 1024); + $count = 0; + foreach ($lines as $line) { + simpletest_generate_file('binary-' . $count++, 64, $line, 'binary'); + } + + // Generate ASCII text test files. + $lines = array(16, 256, 1024, 2048, 20480); + $count = 0; + foreach ($lines as $line) { + simpletest_generate_file('text-' . $count++, 64, $line, 'text'); + } + + // Copy other test files from simpletest. + $original = drupal_get_path('module', 'simpletest') . '/files'; + $files = file_scan_directory($original, '/(html|image|javascript|php|sql)-.*/'); + foreach ($files as $file) { + file_unmanaged_copy($file->uri, PublicStream::basePath()); + } + + $this->generatedTestFiles = TRUE; + } + + $files = array(); + // Make sure type is valid. + if (in_array($type, array('binary', 'html', 'image', 'javascript', 'php', 'sql', 'text'))) { + $files = file_scan_directory('public://', '/' . $type . '\-.*/'); + + // If size is set then remove any files that are not of that size. + if ($size !== NULL) { + foreach ($files as $file) { + $stats = stat($file->uri); + if ($stats['size'] != $size) { + unset($files[$file->uri]); + } + } + } + } + usort($files, array($this, 'drupalCompareFiles')); + return $files; + } + + /** + * Compare two files based on size and file name. + * + * @param object $file1 + * An object with the properties 'uri', 'filename', and 'name'. + * @param object $file2 + * An object with the properties 'uri', 'filename', and 'name'. + * @return bool + * The comparison result based on size and file name. + */ + protected function drupalCompareFiles($file1, $file2) { + $compare_size = filesize($file1->uri) - filesize($file2->uri); + if ($compare_size) { + // Sort by file size. + return $compare_size; + } + else { + // The files were the same size, so sort alphabetically. + return strnatcmp($file1->name, $file2->name); + } + } + +} only in patch2: unchanged: --- a/core/modules/simpletest/src/WebTestBase.php +++ b/core/modules/simpletest/src/WebTestBase.php @@ -22,7 +22,6 @@ use Drupal\Core\Session\AnonymousUserSession; use Drupal\Core\Session\UserSession; use Drupal\Core\Site\Settings; -use Drupal\Core\StreamWrapper\PublicStream; use Drupal\Core\Test\AssertMailTrait; use Drupal\Core\Url; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -56,6 +55,9 @@ createRole as drupalCreateRole; createAdminRole as drupalCreateAdminRole; } + use TestFilesTrait { + getTestFiles as drupalGetTestFiles; + } /** * The profile to install as a basis for testing. @@ -167,11 +169,6 @@ protected $sessionId = NULL; /** - * Whether the files were copied to the test files directory. - */ - protected $generatedTestFiles = FALSE; - - /** * The maximum number of redirects to follow when handling responses. */ protected $maximumRedirects = 5; @@ -332,99 +329,6 @@ protected function findBlockInstance(Block $block) { } /** - * Gets a list of files that can be used in tests. - * - * The first time this method is called, it will call - * simpletest_generate_file() to generate binary and ASCII text files in the - * public:// directory. It will also copy all files in - * core/modules/simpletest/files to public://. These contain image, SQL, PHP, - * JavaScript, and HTML files. - * - * All filenames are prefixed with their type and have appropriate extensions: - * - text-*.txt - * - binary-*.txt - * - html-*.html and html-*.txt - * - image-*.png, image-*.jpg, and image-*.gif - * - javascript-*.txt and javascript-*.script - * - php-*.txt and php-*.php - * - sql-*.txt and sql-*.sql - * - * Any subsequent calls will not generate any new files, or copy the files - * over again. However, if a test class adds a new file to public:// that - * is prefixed with one of the above types, it will get returned as well, even - * on subsequent calls. - * - * @param $type - * File type, possible values: 'binary', 'html', 'image', 'javascript', - * 'php', 'sql', 'text'. - * @param $size - * (optional) File size in bytes to match. Defaults to NULL, which will not - * filter the returned list by size. - * - * @return - * List of files in public:// that match the filter(s). - */ - protected function drupalGetTestFiles($type, $size = NULL) { - if (empty($this->generatedTestFiles)) { - // Generate binary test files. - $lines = array(64, 1024); - $count = 0; - foreach ($lines as $line) { - simpletest_generate_file('binary-' . $count++, 64, $line, 'binary'); - } - - // Generate ASCII text test files. - $lines = array(16, 256, 1024, 2048, 20480); - $count = 0; - foreach ($lines as $line) { - simpletest_generate_file('text-' . $count++, 64, $line, 'text'); - } - - // Copy other test files from simpletest. - $original = drupal_get_path('module', 'simpletest') . '/files'; - $files = file_scan_directory($original, '/(html|image|javascript|php|sql)-.*/'); - foreach ($files as $file) { - file_unmanaged_copy($file->uri, PublicStream::basePath()); - } - - $this->generatedTestFiles = TRUE; - } - - $files = array(); - // Make sure type is valid. - if (in_array($type, array('binary', 'html', 'image', 'javascript', 'php', 'sql', 'text'))) { - $files = file_scan_directory('public://', '/' . $type . '\-.*/'); - - // If size is set then remove any files that are not of that size. - if ($size !== NULL) { - foreach ($files as $file) { - $stats = stat($file->uri); - if ($stats['size'] != $size) { - unset($files[$file->uri]); - } - } - } - } - usort($files, array($this, 'drupalCompareFiles')); - return $files; - } - - /** - * Compare two files based on size and file name. - */ - protected function drupalCompareFiles($file1, $file2) { - $compare_size = filesize($file1->uri) - filesize($file2->uri); - if ($compare_size) { - // Sort by file size. - return $compare_size; - } - else { - // The files were the same size, so sort alphabetically. - return strnatcmp($file1->name, $file2->name); - } - } - - /** * Log in a user with the internal browser. * * If a user is already logged in, then the current user is logged out before