diff --git a/core/modules/image/src/Tests/ImageFieldTestBase.php b/core/modules/image/src/Tests/ImageFieldTestBase.php index c023917e36..6ac64b81ec 100644 --- a/core/modules/image/src/Tests/ImageFieldTestBase.php +++ b/core/modules/image/src/Tests/ImageFieldTestBase.php @@ -2,6 +2,8 @@ namespace Drupal\image\Tests; +@trigger_error('The ' . __NAMESPACE__ . '\ImageFieldTestBase is deprecated in Drupal 8.4.x and will be removed before Drupal 9.0.0. Use \Drupal\Tests\image\Functional\ImageFieldTestBase instead. See https://www.drupal.org/node/2863626.', E_USER_DEPRECATED); + use Drupal\Tests\image\Kernel\ImageFieldCreationTrait; use Drupal\simpletest\WebTestBase; diff --git a/core/modules/image/src/Tests/FileMoveTest.php b/core/modules/image/tests/src/Functional/FileMoveTest.php similarity index 83% rename from core/modules/image/src/Tests/FileMoveTest.php rename to core/modules/image/tests/src/Functional/FileMoveTest.php index d9ce27f75d..3cf3be71b8 100644 --- a/core/modules/image/src/Tests/FileMoveTest.php +++ b/core/modules/image/tests/src/Functional/FileMoveTest.php @@ -1,17 +1,23 @@ drupalGet("admin/structure/types/manage/article/display"); // Test for existence of link to image styles configuration. - $this->drupalPostAjaxForm(NULL, [], "{$field_name}_settings_edit"); + $this->drupalPostForm(NULL, [], "{$field_name}_settings_edit"); $this->assertLinkByHref(\Drupal::url('entity.image_style.collection'), 0, 'Link to image styles configuration is found'); // Remove 'administer image styles' permission from testing admin user. @@ -65,7 +73,7 @@ public function _testImageFieldFormatters($scheme) { $this->drupalGet("admin/structure/types/manage/article/display"); // Test for absence of link to image styles configuration. - $this->drupalPostAjaxForm(NULL, [], "{$field_name}_settings_edit"); + $this->drupalPostForm(NULL, [], "{$field_name}_settings_edit"); $this->assertNoLinkByHref(\Drupal::url('entity.image_style.collection'), 'Link to image styles configuration is absent when permissions are insufficient'); // Restore 'administer image styles' permission to testing admin user @@ -258,8 +266,11 @@ public function testImageFieldSettings() { $nid = $this->uploadNodeImage($test_image, $field_name, 'article', $alt); $this->drupalGet('node/' . $nid . '/edit'); - $this->assertFieldByName($field_name . '[0][alt]', '', 'Alt field displayed on article form.'); + + // Verify that the optional fields alt & title are saved & filled. + $this->assertFieldByName($field_name . '[0][alt]', $alt, 'Alt field displayed on article form.'); $this->assertFieldByName($field_name . '[0][title]', '', 'Title field displayed on article form.'); + // Verify that the attached image is being previewed using the 'medium' // style. $node_storage->resetCache([$nid]); @@ -324,9 +335,9 @@ public function testImageFieldSettings() { $edit = [ 'files[' . $field_name . '_2][]' => drupal_realpath($test_image->uri), ]; - $this->drupalPostAjaxForm(NULL, $edit, $field_name . '_2_upload_button'); - $this->assertNoRaw(''); - $this->assertRaw(''); + $this->drupalPostForm(NULL, $edit, $field_name . '_2_upload_button'); + $this->assertSession()->elementNotExists('css', 'input[name="files[' . $field_name . '_2][]"]'); + $this->assertSession()->elementExists('css', 'input[name="files[' . $field_name . '_3][]"]'); } /** @@ -410,10 +421,11 @@ public function testImageFieldDefaultImage() { $this->assertRaw($image_output, 'User supplied image is displayed.'); // Remove default image from the field and make sure it is no longer used. - $edit = [ - 'settings[default_image][uuid][fids]' => 0, - ]; - $this->drupalPostForm("admin/structure/types/manage/article/fields/node.article.$field_name/storage", $edit, t('Save field settings')); + // Can't use fillField cause Mink can't fill hidden fields. + $this->drupalGet("admin/structure/types/manage/article/fields/node.article.$field_name/storage"); + $this->getSession()->getPage()->find('css', 'input[name="settings[default_image][uuid][fids]"]')->setValue(0); + $this->getSession()->getPage()->pressButton(t('Save field settings')); + // Clear field definition cache so the new default image is detected. \Drupal::entityManager()->clearCachedFieldDefinitions(); $field_storage = FieldStorageConfig::loadByName('node', $field_name); diff --git a/core/modules/image/tests/src/Functional/ImageFieldTestBase.php b/core/modules/image/tests/src/Functional/ImageFieldTestBase.php index 5fa0614040..7a226dc3b3 100644 --- a/core/modules/image/tests/src/Functional/ImageFieldTestBase.php +++ b/core/modules/image/tests/src/Functional/ImageFieldTestBase.php @@ -86,11 +86,12 @@ public function uploadNodeImage($image, $field_name, $type, $alt = '') { $edit = [ 'title[0][value]' => $this->randomMachineName(), ]; + $edit['files[' . $field_name . '_0]'] = drupal_realpath($image->uri); - $this->drupalPostForm('node/add/' . $type, $edit, t('Save and publish')); + $this->drupalPostForm('node/add/' . $type, $edit, t('Save')); if ($alt) { // Add alt text. - $this->drupalPostForm(NULL, [$field_name . '[0][alt]' => $alt], t('Save and publish')); + $this->drupalPostForm(NULL, [$field_name . '[0][alt]' => $alt], t('Save')); } // Retrieve ID of the newly created node from the current URL. diff --git a/core/modules/image/src/Tests/ImageFieldValidateTest.php b/core/modules/image/tests/src/Functional/ImageFieldValidateTest.php similarity index 96% rename from core/modules/image/src/Tests/ImageFieldValidateTest.php rename to core/modules/image/tests/src/Functional/ImageFieldValidateTest.php index e429d0bb20..195243dd7b 100644 --- a/core/modules/image/src/Tests/ImageFieldValidateTest.php +++ b/core/modules/image/tests/src/Functional/ImageFieldValidateTest.php @@ -1,6 +1,8 @@ drupalGet($generate_url); $this->assertResponse(200, 'Image was generated at the URL.'); $this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.'); - $this->assertRaw(file_get_contents($generated_uri), 'URL returns expected file.'); + // assertRaw can't be used with string containing non UTF-8 chars. + $this->assertNotEmpty(file_get_contents($generated_uri), 'URL returns expected file.'); $image = $this->container->get('image.factory')->get($generated_uri); $this->assertEqual($this->drupalGetHeader('Content-Type'), $image->getMimeType(), 'Expected Content-Type was reported.'); $this->assertEqual($this->drupalGetHeader('Content-Length'), $image->getFileSize(), 'Expected Content-Length was reported.'); @@ -199,7 +206,9 @@ public function doImageStyleUrlAndPathTests($scheme, $clean_url = TRUE, $extra_s else { // Check for PNG-Signature (cf. http://www.libpng.org/pub/png/book/chapter08.html#png.ch08.div.2) in the // response body. - $this->assertNoRaw(chr(137) . chr(80) . chr(78) . chr(71) . chr(13) . chr(10) . chr(26) . chr(10), 'No PNG signature found in the response body.'); + $raw = $this->getSession()->getPage()->getContent(); + $result = strpos($raw, chr(137) . chr(80) . chr(78) . chr(71) . chr(13) . chr(10) . chr(26) . chr(10)); + $this->assertEqual($result === FALSE, TRUE); } } else { diff --git a/core/modules/image/src/Tests/ImageThemeFunctionTest.php b/core/modules/image/tests/src/Functional/ImageThemeFunctionTest.php similarity index 62% rename from core/modules/image/src/Tests/ImageThemeFunctionTest.php rename to core/modules/image/tests/src/Functional/ImageThemeFunctionTest.php index 54c5c64a19..34b7cce08e 100644 --- a/core/modules/image/src/Tests/ImageThemeFunctionTest.php +++ b/core/modules/image/tests/src/Functional/ImageThemeFunctionTest.php @@ -1,6 +1,6 @@ setRawContent($renderer->renderRoot($element)); - $elements = $this->xpath('//a[@href=:path]/img[@class="image-style-test" and @src=:url and @width=:width and @height=:height]', [':path' => base_path() . $path, ':url' => $url, ':width' => $image->getWidth(), ':height' => $image->getHeight()]); - $this->assertEqual(count($elements), 1, 'theme_image_formatter() correctly renders with a NULL value for the alt option.'); + + $html = $renderer->renderRoot($element); + $elements = $this->getXPathResultCount('//a[@href="'.base_path() . $path.'"]/img[@class="image-style-test" and @src="'.$url.'" and @width="'.$image->getWidth().'" and @height="'.$image->getHeight().'"]', $html); + $this->assertEqual($elements, 1, 'theme_image_formatter() correctly renders with a NULL value for the alt option.'); // Test using theme_image_formatter() without an image title, alt text, or // link options. $element = $base_element; $element['#item']->alt = ''; - $this->setRawContent($renderer->renderRoot($element)); - $elements = $this->xpath('//a[@href=:path]/img[@class="image-style-test" and @src=:url and @width=:width and @height=:height and @alt=""]', [':path' => base_path() . $path, ':url' => $url, ':width' => $image->getWidth(), ':height' => $image->getHeight()]); - $this->assertEqual(count($elements), 1, 'theme_image_formatter() correctly renders without title, alt, or path options.'); + + $html = $renderer->renderRoot($element); + $elements = $this->getXPathResultCount('//a[@href="'.base_path() . $path.'"]/img[@class="image-style-test" and @src="'.$url.'" and @width="'.$image->getWidth().'" and @height="'.$image->getHeight().'"]', $html); + $this->assertEqual($elements, 1, 'theme_image_formatter() correctly renders without title, alt, or path options.'); // Link the image to a fragment on the page, and not a full URL. $fragment = $this->randomMachineName(); $element = $base_element; $element['#url'] = Url::fromRoute('', [], ['fragment' => $fragment]); - $this->setRawContent($renderer->renderRoot($element)); - $elements = $this->xpath('//a[@href=:fragment]/img[@class="image-style-test" and @src=:url and @width=:width and @height=:height and @alt=""]', [ - ':fragment' => '#' . $fragment, - ':url' => $url, - ':width' => $image->getWidth(), - ':height' => $image->getHeight() - ]); - $this->assertEqual(count($elements), 1, 'theme_image_formatter() correctly renders a link fragment.'); + + $html = $renderer->renderRoot($element); + $elements = $this->getXPathResultCount('//a[@href="'.'#' . $fragment.'"]/img[@class="image-style-test" and @src="'.$url.'" and @width="'.$image->getWidth().'" and @height="'.$image->getHeight().'"]', $html); + $this->assertEqual($elements, 1, 'theme_image_formatter() correctly renders a link fragment.'); } /** @@ -146,16 +150,19 @@ public function testImageStyleTheme() { ]; $element = $base_element; - $this->setRawContent($renderer->renderRoot($element)); - $elements = $this->xpath('//img[@class="image-style-image-test" and @src=:url and @alt=""]', [':url' => $url]); - $this->assertEqual(count($elements), 1, 'theme_image_style() renders an image correctly.'); + + $html = $renderer->renderRoot($element); + $elements = $this->getXPathResultCount('//img[@class="image-style-image-test" and @src="'.$url.'" and @alt=""]', $html); + $this->assertEqual($elements, 1, 'theme_image_style() renders an image correctly.'); // Test using theme_image_style() with a NULL value for the alt option. $element = $base_element; $element['#alt'] = NULL; - $this->setRawContent($renderer->renderRoot($element)); - $elements = $this->xpath('//img[@class="image-style-image-test" and @src=:url]', [':url' => $url]); - $this->assertEqual(count($elements), 1, 'theme_image_style() renders an image correctly with a NULL value for the alt option.'); + + $html = $renderer->renderRoot($element); + $elements = $this->getXPathResultCount('//img[@class="image-style-image-test" and @src="'.$url.'" and not(@alt)]', $html); + + $this->assertEqual($elements, 1, 'theme_image_style() renders an image correctly whitout alt option when NULL.'); } /** @@ -176,9 +183,9 @@ public function testImageAltFunctionality() { '#attributes' => ['class' => 'image-with-regular-alt', 'id' => 'my-img'], ]; - $this->setRawContent($renderer->renderRoot($image_with_alt_property)); - $elements = $this->xpath('//img[contains(@class, class) and contains(@alt, :alt)]', [":class" => "image-with-regular-alt", ":alt" => "Regular alt"]); - $this->assertEqual(count($elements), 1, 'Regular alt displays correctly'); + $html = $renderer->renderRoot($image_with_alt_property); + $elements = $this->getXPathResultCount('//img[contains(@class, "image-with-regular-alt") and contains(@alt, "Regular alt")]', $html); + $this->assertEqual($elements, 1, 'Regular alt displays correctly'); // Test using alt attribute inside attributes. $image_with_alt_attribute_alt_attribute = [ @@ -194,9 +201,9 @@ public function testImageAltFunctionality() { ], ]; - $this->setRawContent($renderer->renderRoot($image_with_alt_attribute_alt_attribute)); - $elements = $this->xpath('//img[contains(@class, class) and contains(@alt, :alt)]', [":class" => "image-with-attribute-alt", ":alt" => "Attribute alt"]); - $this->assertEqual(count($elements), 1, 'Attribute alt displays correctly'); + $html = $renderer->renderRoot($image_with_alt_attribute_alt_attribute); + $elements = $this->getXPathResultCount('//img[contains(@class, "image-with-attribute-alt") and contains(@alt, "Attribute alt")]', $html); + $this->assertEqual($elements, 1, 'Attribute alt displays correctly'); // Test using alt attribute as property and inside attributes. $image_with_alt_attribute_both = [ @@ -213,9 +220,28 @@ public function testImageAltFunctionality() { ], ]; - $this->setRawContent($renderer->renderRoot($image_with_alt_attribute_both)); - $elements = $this->xpath('//img[contains(@class, class) and contains(@alt, :alt)]', [":class" => "image-with-attribute-alt", ":alt" => "Attribute alt"]); - $this->assertEqual(count($elements), 1, 'Attribute alt overrides alt property if both set.'); + $html = $renderer->renderRoot($image_with_alt_attribute_both); + $elements = $this->getXPathResultCount('//img[contains(@class, "image-with-attribute-alt") and contains(@alt, "Attribute alt")]', $html); + $this->assertEqual($elements, 1, 'Attribute alt overrides alt property if both set.'); + } + + /** + * Counts the occurrences of the given XPath query in a given HTML snippet. + * + * @param string $query + * The XPath query to execute. + * @param string $html + * The HTML snippet to check. + * + * @return int + * The number of results that are found. + */ + protected function getXPathResultCount($query, $html) { + $document = new \DOMDocument(); + $document->loadHTML($html); + $xpath = new \DOMXPath($document); + + return $xpath->query($query)->length; } } diff --git a/core/modules/image/src/Tests/QuickEditImageControllerTest.php b/core/modules/image/tests/src/Functional/QuickEditImageControllerTest.php similarity index 65% rename from core/modules/image/src/Tests/QuickEditImageControllerTest.php rename to core/modules/image/tests/src/Functional/QuickEditImageControllerTest.php index 870c1bb5d5..1fe99fde4b 100644 --- a/core/modules/image/src/Tests/QuickEditImageControllerTest.php +++ b/core/modules/image/tests/src/Functional/QuickEditImageControllerTest.php @@ -1,18 +1,23 @@ drupalGet('quickedit/image/info/node/' . $node->id() . '/' . $this->fieldName . '/' . $node->language()->getId() . '/default'); $this->assertResponse('403'); - $this->drupalPost('quickedit/image/upload/node/' . $node->id() . '/' . $this->fieldName . '/' . $node->language()->getId() . '/default', 'application/json', []); - $this->assertResponse('403'); + + /** @var \Symfony\Component\BrowserKit\Client $client */ + $client = $this->getSession()->getDriver()->getClient(); + $client->request('POST', '/quickedit/image/upload/node/' . $node->id() . '/' . $this->fieldName . '/' . $node->language()->getId() . '/default'); + $this->assertEquals('403', $client->getResponse()->getStatus()); } /** @@ -118,8 +126,13 @@ public function testValidImageUpload() { } } $this->assertTrue($valid_image); - $this->uploadImage($valid_image, $node->id(), $this->fieldName, $node->language()->getId()); - $this->assertText('fid', t('Valid upload completed successfully.')); + + $this->drupalLogin($this->contentAuthorUser); + $response = $this->uploadImage($valid_image, $node->id(), $this->fieldName, $node->language()->getId()); + + $this->assertEqual($response->getStatusCode(), 200); + $this->assertEqual($response->getReasonPhrase(), 'OK'); + $this->assertContains('"fid":"1"', $response->getBody()->getContents(), t('Valid upload completed successfully.')); } /** @@ -145,8 +158,13 @@ public function testInvalidUpload() { } } $this->assertTrue($invalid_image); - $this->uploadImage($invalid_image, $node->id(), $this->fieldName, $node->language()->getId()); - $this->assertText('main_error', t('Invalid upload returned errors.')); + + $this->drupalLogin($this->contentAuthorUser); + $response = $this->uploadImage($invalid_image, $node->id(), $this->fieldName, $node->language()->getId()); + + $this->assertEqual($response->getStatusCode(), 200); + $this->assertEqual($response->getReasonPhrase(), 'OK'); + $this->assertContains('"main_error":"The image failed validation."', $response->getBody()->getContents(), t('Invalid upload returned errors.')); } /** @@ -161,26 +179,42 @@ public function testInvalidUpload() { * @param string $langcode * The langcode to use when setting the field's value. * - * @return mixed - * The content returned from the call to $this->curlExec(). + * @return \Psr\Http\Message\ResponseInterface + * The request response. */ public 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', + $client = $this->getHttpClient(); + // Perform HTTP request. + return $client->post($this->buildUrl($path, []), [ + 'multipart' => [ + [ + 'name' => 'files[image]', + 'contents' => fopen($filepath, 'r') + ] ], + 'http_errors' => FALSE, + 'cookies' => $this->cookies, ]); } + /** + * Obtain the HTTP client and set the cookies. + * + * @return \GuzzleHttp\Client + * The client with BrowserTestBase configuration. + */ + protected function getHttpClient() { + // Similar code is also employed to test CSRF tokens. + // @see \Drupal\Tests\system\Functional\CsrfRequestHeaderTest::testRouteAccess() + $domain = parse_url($this->getUrl(), PHP_URL_HOST); + $session_id = $this->getSession()->getCookie($this->getSessionName()); + $this->cookies = CookieJar::fromArray([$this->getSessionName() => $session_id], $domain); + return $this->getSession()->getDriver()->getClient()->getClient(); + } + } diff --git a/core/tests/Drupal/Tests/BrowserTestBase.php b/core/tests/Drupal/Tests/BrowserTestBase.php index abdedd9c03..727020af5b 100644 --- a/core/tests/Drupal/Tests/BrowserTestBase.php +++ b/core/tests/Drupal/Tests/BrowserTestBase.php @@ -669,6 +669,43 @@ protected function drupalGet($path, array $options = [], array $headers = []) { return $out; } + /** + * Retrieves a Drupal path or an absolute path and JSON decodes the result. + * + * @param \Drupal\Core\Url|string $path + * Drupal path or URL to request AJAX from. + * @param array $options + * Array of URL options. + * @param array $headers + * Array of headers. Eg array('Accept: application/vnd.drupal-ajax'). + * + * @return array + * Decoded json. + */ + protected function drupalGetJSON($path, array $options = [], array $headers = []) { + return Json::decode($this->drupalGetWithFormat($path, 'json', $options, $headers)); + } + + /** + * Retrieves a Drupal path or an absolute path for a given format. + * + * @param \Drupal\Core\Url|string $path + * Drupal path or URL to request given format from. + * @param string $format + * The wanted request format. + * @param array $options + * Array of URL options. + * @param array $headers + * Array of headers. + * + * @return mixed + * The result of the request. + */ + protected function drupalGetWithFormat($path, $format, array $options = [], array $headers = []) { + $options += ['query' => ['_format' => $format]]; + return $this->drupalGet($path, $options, $headers); + } + /** * Takes a path and returns an absolute path. *