Chris@0: adminUser = $this->drupalCreateUser([ Chris@0: 'administer responsive images', Chris@0: 'access content', Chris@0: 'access administration pages', Chris@0: 'administer site configuration', Chris@0: 'administer content types', Chris@0: 'administer node display', Chris@0: 'administer nodes', Chris@0: 'create article content', Chris@0: 'edit any article content', Chris@0: 'delete any article content', Chris@0: 'administer image styles' Chris@0: ]); Chris@0: $this->drupalLogin($this->adminUser); Chris@0: // Add responsive image style. Chris@0: $this->responsiveImgStyle = ResponsiveImageStyle::create([ Chris@0: 'id' => 'style_one', Chris@0: 'label' => 'Style One', Chris@0: 'breakpoint_group' => 'responsive_image_test_module', Chris@0: 'fallback_image_style' => 'large', Chris@0: ]); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Tests responsive image formatters on node display for public files. Chris@0: */ Chris@0: public function testResponsiveImageFieldFormattersPublic() { Chris@0: $this->addTestImageStyleMappings(); Chris@0: $this->doTestResponsiveImageFieldFormatters('public'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Tests responsive image formatters on node display for private files. Chris@0: */ Chris@0: public function testResponsiveImageFieldFormattersPrivate() { Chris@0: $this->addTestImageStyleMappings(); Chris@0: // Remove access content permission from anonymous users. Chris@0: user_role_change_permissions(RoleInterface::ANONYMOUS_ID, ['access content' => FALSE]); Chris@0: $this->doTestResponsiveImageFieldFormatters('private'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Test responsive image formatters when image style is empty. Chris@0: */ Chris@0: public function testResponsiveImageFieldFormattersEmptyStyle() { Chris@0: $this->addTestImageStyleMappings(TRUE); Chris@0: $this->doTestResponsiveImageFieldFormatters('public', TRUE); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Add image style mappings to the responsive image style entity. Chris@0: * Chris@0: * @param bool $empty_styles Chris@0: * If true, the image style mappings will get empty image styles. Chris@0: */ Chris@0: protected function addTestImageStyleMappings($empty_styles = FALSE) { Chris@0: if ($empty_styles) { Chris@0: $this->responsiveImgStyle Chris@0: ->addImageStyleMapping('responsive_image_test_module.mobile', '1x', [ Chris@0: 'image_mapping_type' => 'image_style', Chris@0: 'image_mapping' => '', Chris@0: ]) Chris@0: ->addImageStyleMapping('responsive_image_test_module.narrow', '1x', [ Chris@0: 'image_mapping_type' => 'sizes', Chris@0: 'image_mapping' => [ Chris@0: 'sizes' => '(min-width: 700px) 700px, 100vw', Chris@0: 'sizes_image_styles' => [], Chris@0: ], Chris@0: ]) Chris@0: ->addImageStyleMapping('responsive_image_test_module.wide', '1x', [ Chris@0: 'image_mapping_type' => 'image_style', Chris@0: 'image_mapping' => '', Chris@0: ]) Chris@0: ->save(); Chris@0: } Chris@0: else { Chris@0: $this->responsiveImgStyle Chris@0: // Test the output of an empty image. Chris@0: ->addImageStyleMapping('responsive_image_test_module.mobile', '1x', [ Chris@0: 'image_mapping_type' => 'image_style', Chris@0: 'image_mapping' => RESPONSIVE_IMAGE_EMPTY_IMAGE, Chris@0: ]) Chris@0: // Test the output with a 1.5x multiplier. Chris@0: ->addImageStyleMapping('responsive_image_test_module.mobile', '1.5x', [ Chris@0: 'image_mapping_type' => 'image_style', Chris@0: 'image_mapping' => 'thumbnail', Chris@0: ]) Chris@0: // Test the output of the 'sizes' attribute. Chris@0: ->addImageStyleMapping('responsive_image_test_module.narrow', '1x', [ Chris@0: 'image_mapping_type' => 'sizes', Chris@0: 'image_mapping' => [ Chris@0: 'sizes' => '(min-width: 700px) 700px, 100vw', Chris@0: 'sizes_image_styles' => [ Chris@0: 'large', Chris@0: 'medium', Chris@0: ], Chris@0: ], Chris@0: ]) Chris@0: // Test the normal output of mapping to an image style. Chris@0: ->addImageStyleMapping('responsive_image_test_module.wide', '1x', [ Chris@0: 'image_mapping_type' => 'image_style', Chris@0: 'image_mapping' => 'large', Chris@0: ]) Chris@0: // Test the output of the original image. Chris@0: ->addImageStyleMapping('responsive_image_test_module.wide', '3x', [ Chris@0: 'image_mapping_type' => 'image_style', Chris@0: 'image_mapping' => RESPONSIVE_IMAGE_ORIGINAL_IMAGE, Chris@0: ]) Chris@0: ->save(); Chris@0: } Chris@0: } Chris@0: /** Chris@0: * Test responsive image formatters on node display. Chris@0: * Chris@0: * If the empty styles param is set, then the function only tests for the Chris@0: * fallback image style (large). Chris@0: * Chris@0: * @param string $scheme Chris@0: * File scheme to use. Chris@0: * @param bool $empty_styles Chris@0: * If true, use an empty string for image style names. Chris@0: * Defaults to false. Chris@0: */ Chris@0: protected function doTestResponsiveImageFieldFormatters($scheme, $empty_styles = FALSE) { Chris@0: /** @var \Drupal\Core\Render\RendererInterface $renderer */ Chris@0: $renderer = $this->container->get('renderer'); Chris@0: $node_storage = $this->container->get('entity.manager')->getStorage('node'); Chris@0: $field_name = Unicode::strtolower($this->randomMachineName()); Chris@0: $this->createImageField($field_name, 'article', ['uri_scheme' => $scheme]); Chris@0: // Create a new node with an image attached. Make sure we use a large image Chris@0: // so the scale effects of the image styles always have an effect. Chris@0: $test_image = current($this->drupalGetTestFiles('image', 39325)); Chris@0: Chris@0: // Create alt text for the image. Chris@0: $alt = $this->randomMachineName(); Chris@0: Chris@0: $nid = $this->uploadNodeImage($test_image, $field_name, 'article', $alt); Chris@0: $node_storage->resetCache([$nid]); Chris@0: $node = $node_storage->load($nid); Chris@0: Chris@0: // Test that the default formatter is being used. Chris@0: $image_uri = File::load($node->{$field_name}->target_id)->getFileUri(); Chris@0: $image = [ Chris@0: '#theme' => 'image', Chris@0: '#uri' => $image_uri, Chris@0: '#width' => 360, Chris@0: '#height' => 240, Chris@0: '#alt' => $alt, Chris@0: ]; Chris@0: $default_output = str_replace("\n", NULL, $renderer->renderRoot($image)); Chris@0: $this->assertRaw($default_output, 'Default formatter displaying correctly on full node view.'); Chris@0: Chris@0: // Test field not being configured. This should not cause a fatal error. Chris@0: $display_options = [ Chris@0: 'type' => 'responsive_image_test', Chris@0: 'settings' => ResponsiveImageFormatter::defaultSettings(), Chris@0: ]; Chris@0: $display = $this->container->get('entity.manager') Chris@0: ->getStorage('entity_view_display') Chris@0: ->load('node.article.default'); Chris@0: if (!$display) { Chris@0: $values = [ Chris@0: 'targetEntityType' => 'node', Chris@0: 'bundle' => 'article', Chris@0: 'mode' => 'default', Chris@0: 'status' => TRUE, Chris@0: ]; Chris@0: $display = $this->container->get('entity.manager')->getStorage('entity_view_display')->create($values); Chris@0: } Chris@0: $display->setComponent($field_name, $display_options)->save(); Chris@0: Chris@0: $this->drupalGet('node/' . $nid); Chris@0: Chris@0: // Test theme function for responsive image, but using the test formatter. Chris@0: $display_options = [ Chris@0: 'type' => 'responsive_image_test', Chris@0: 'settings' => [ Chris@0: 'image_link' => 'file', Chris@0: 'responsive_image_style' => 'style_one', Chris@0: ], Chris@0: ]; Chris@0: $display = entity_get_display('node', 'article', 'default'); Chris@0: $display->setComponent($field_name, $display_options) Chris@0: ->save(); Chris@0: Chris@0: $this->drupalGet('node/' . $nid); Chris@0: Chris@0: // Use the responsive image formatter linked to file formatter. Chris@0: $display_options = [ Chris@0: 'type' => 'responsive_image', Chris@0: 'settings' => [ Chris@0: 'image_link' => 'file', Chris@0: 'responsive_image_style' => 'style_one', Chris@0: ], Chris@0: ]; Chris@0: $display = entity_get_display('node', 'article', 'default'); Chris@0: $display->setComponent($field_name, $display_options) Chris@0: ->save(); Chris@0: Chris@0: $default_output = 'drupalGet('node/' . $nid); Chris@0: $cache_tags_header = $this->drupalGetHeader('X-Drupal-Cache-Tags'); Chris@0: $this->assertTrue(!preg_match('/ image_style\:/', $cache_tags_header), 'No image style cache tag found.'); Chris@0: Chris@0: $this->removeWhiteSpace(); Chris@0: $this->assertRaw($default_output, 'Image linked to file formatter displaying correctly on full node view.'); Chris@0: // Verify that the image can be downloaded. Chris@0: $this->assertEqual(file_get_contents($test_image->uri), $this->drupalGet(file_create_url($image_uri)), 'File was downloaded successfully.'); Chris@0: if ($scheme == 'private') { Chris@0: // Only verify HTTP headers when using private scheme and the headers are Chris@0: // sent by Drupal. Chris@0: $this->assertEqual($this->drupalGetHeader('Content-Type'), 'image/png', 'Content-Type header was sent.'); Chris@0: $this->assertTrue(strstr($this->drupalGetHeader('Cache-Control'), 'private') !== FALSE, 'Cache-Control header was sent.'); Chris@0: Chris@0: // Log out and try to access the file. Chris@0: $this->drupalLogout(); Chris@0: $this->drupalGet(file_create_url($image_uri)); Chris@0: $this->assertResponse('403', 'Access denied to original image as anonymous user.'); Chris@0: Chris@0: // Log in again. Chris@0: $this->drupalLogin($this->adminUser); Chris@0: } Chris@0: Chris@0: // Use the responsive image formatter with a responsive image style. Chris@0: $display_options['settings']['responsive_image_style'] = 'style_one'; Chris@0: $display_options['settings']['image_link'] = ''; Chris@0: $display->setComponent($field_name, $display_options) Chris@0: ->save(); Chris@0: Chris@0: // Create a derivative so at least one MIME type will be known. Chris@0: $large_style = ImageStyle::load('large'); Chris@0: $large_style->createDerivative($image_uri, $large_style->buildUri($image_uri)); Chris@0: Chris@0: // Output should contain all image styles and all breakpoints. Chris@0: $this->drupalGet('node/' . $nid); Chris@0: if (!$empty_styles) { Chris@0: $this->assertRaw('/styles/medium/'); Chris@0: // Make sure the IE9 workaround is present. Chris@0: $this->assertRaw(''); Chris@0: $this->assertRaw(''); Chris@0: // Assert the empty image is present. Chris@0: $this->assertRaw('data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='); Chris@0: $thumbnail_style = ImageStyle::load('thumbnail'); Chris@0: // Assert the output of the 'srcset' attribute (small multipliers first). Chris@0: $this->assertRaw('data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== 1x, ' . file_url_transform_relative($thumbnail_style->buildUrl($image_uri)) . ' 1.5x'); Chris@0: $this->assertRaw('/styles/medium/'); Chris@0: // Assert the output of the original image. Chris@0: $this->assertRaw(file_url_transform_relative(file_create_url($image_uri)) . ' 3x'); Chris@0: // Assert the output of the breakpoints. Chris@0: $this->assertRaw('media="(min-width: 0px)"'); Chris@0: $this->assertRaw('media="(min-width: 560px)"'); Chris@0: // Assert the output of the 'sizes' attribute. Chris@0: $this->assertRaw('sizes="(min-width: 700px) 700px, 100vw"'); Chris@0: $this->assertPattern('/media="\(min-width: 560px\)".+?sizes="\(min-width: 700px\) 700px, 100vw"/'); Chris@0: // Assert the output of the 'srcset' attribute (small images first). Chris@0: $medium_style = ImageStyle::load('medium'); Chris@0: $this->assertRaw(file_url_transform_relative($medium_style->buildUrl($image_uri)) . ' 220w, ' . file_url_transform_relative($large_style->buildUrl($image_uri)) . ' 360w'); Chris@0: $this->assertRaw('media="(min-width: 851px)"'); Chris@0: } Chris@0: $this->assertRaw('/styles/large/'); Chris@0: $cache_tags = explode(' ', $this->drupalGetHeader('X-Drupal-Cache-Tags')); Chris@0: $this->assertTrue(in_array('config:responsive_image.styles.style_one', $cache_tags)); Chris@0: if (!$empty_styles) { Chris@0: $this->assertTrue(in_array('config:image.style.medium', $cache_tags)); Chris@0: $this->assertTrue(in_array('config:image.style.thumbnail', $cache_tags)); Chris@0: $this->assertRaw('type="image/png"'); Chris@0: } Chris@0: $this->assertTrue(in_array('config:image.style.large', $cache_tags)); Chris@0: Chris@0: // Test the fallback image style. Chris@0: $image = \Drupal::service('image.factory')->get($image_uri); Chris@0: $fallback_image = [ Chris@0: '#theme' => 'image', Chris@0: '#alt' => $alt, Chris@0: '#uri' => file_url_transform_relative($large_style->buildUrl($image->getSource())), Chris@0: ]; Chris@0: // The image.html.twig template has a newline after the tag but Chris@0: // responsive-image.html.twig doesn't have one after the fallback image, so Chris@0: // we remove it here. Chris@0: $default_output = trim($renderer->renderRoot($fallback_image)); Chris@0: $this->assertRaw($default_output, 'Image style large formatter displaying correctly on full node view.'); Chris@0: Chris@0: if ($scheme == 'private') { Chris@0: // Log out and try to access the file. Chris@0: $this->drupalLogout(); Chris@0: $this->drupalGet($large_style->buildUrl($image_uri)); Chris@0: $this->assertResponse('403', 'Access denied to image style large as anonymous user.'); Chris@0: $cache_tags_header = $this->drupalGetHeader('X-Drupal-Cache-Tags'); Chris@0: $this->assertTrue(!preg_match('/ image_style\:/', $cache_tags_header), 'No image style cache tag found.'); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Tests responsive image formatters on node display linked to the file. Chris@0: */ Chris@0: public function testResponsiveImageFieldFormattersLinkToFile() { Chris@0: $this->addTestImageStyleMappings(); Chris@0: $this->assertResponsiveImageFieldFormattersLink('file'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Tests responsive image formatters on node display linked to the node. Chris@0: */ Chris@0: public function testResponsiveImageFieldFormattersLinkToNode() { Chris@0: $this->addTestImageStyleMappings(); Chris@0: $this->assertResponsiveImageFieldFormattersLink('content'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Tests responsive image formatter on node display with an empty media query. Chris@0: */ Chris@0: public function testResponsiveImageFieldFormattersEmptyMediaQuery() { Chris@0: $this->responsiveImgStyle Chris@0: // Test the output of an empty media query. Chris@0: ->addImageStyleMapping('responsive_image_test_module.empty', '1x', [ Chris@0: 'image_mapping_type' => 'image_style', Chris@0: 'image_mapping' => RESPONSIVE_IMAGE_EMPTY_IMAGE, Chris@0: ]) Chris@0: // Test the output with a 1.5x multiplier. Chris@0: ->addImageStyleMapping('responsive_image_test_module.mobile', '1x', [ Chris@0: 'image_mapping_type' => 'image_style', Chris@0: 'image_mapping' => 'thumbnail', Chris@0: ]) Chris@0: ->save(); Chris@0: $node_storage = $this->container->get('entity.manager')->getStorage('node'); Chris@0: $field_name = Unicode::strtolower($this->randomMachineName()); Chris@0: $this->createImageField($field_name, 'article', ['uri_scheme' => 'public']); Chris@0: // Create a new node with an image attached. Chris@0: $test_image = current($this->drupalGetTestFiles('image')); Chris@0: $nid = $this->uploadNodeImage($test_image, $field_name, 'article', $this->randomMachineName()); Chris@0: $node_storage->resetCache([$nid]); Chris@0: Chris@0: // Use the responsive image formatter linked to file formatter. Chris@0: $display_options = [ Chris@0: 'type' => 'responsive_image', Chris@0: 'settings' => [ Chris@0: 'image_link' => '', Chris@0: 'responsive_image_style' => 'style_one', Chris@0: ], Chris@0: ]; Chris@0: $display = entity_get_display('node', 'article', 'default'); Chris@0: $display->setComponent($field_name, $display_options) Chris@0: ->save(); Chris@0: Chris@0: // View the node. Chris@0: $this->drupalGet('node/' . $nid); Chris@0: Chris@0: // Assert an empty media attribute is not output. Chris@0: $this->assertNoPattern('@srcset="data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== 1x".+?media=".+?/>load($nid); Chris@0: $image_uri = File::load($node->{$field_name}->target_id)->getFileUri(); Chris@0: $this->assertPattern('/srcset="' . preg_quote(file_url_transform_relative($thumbnail_style->buildUrl($image_uri)), '/') . ' 1x".+?media="\(min-width: 0px\)"/'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Tests responsive image formatter on node display with one source. Chris@0: */ Chris@0: public function testResponsiveImageFieldFormattersOneSource() { Chris@0: $this->responsiveImgStyle Chris@0: // Test the output of an empty media query. Chris@0: ->addImageStyleMapping('responsive_image_test_module.empty', '1x', [ Chris@0: 'image_mapping_type' => 'image_style', Chris@0: 'image_mapping' => 'medium', Chris@0: ]) Chris@0: ->addImageStyleMapping('responsive_image_test_module.empty', '2x', [ Chris@0: 'image_mapping_type' => 'image_style', Chris@0: 'image_mapping' => 'large', Chris@0: ]) Chris@0: ->save(); Chris@0: $node_storage = $this->container->get('entity.manager')->getStorage('node'); Chris@0: $field_name = Unicode::strtolower($this->randomMachineName()); Chris@0: $this->createImageField($field_name, 'article', ['uri_scheme' => 'public']); Chris@0: // Create a new node with an image attached. Chris@0: $test_image = current($this->drupalGetTestFiles('image')); Chris@0: $nid = $this->uploadNodeImage($test_image, $field_name, 'article', $this->randomMachineName()); Chris@0: $node_storage->resetCache([$nid]); Chris@0: Chris@0: // Use the responsive image formatter linked to file formatter. Chris@0: $display_options = [ Chris@0: 'type' => 'responsive_image', Chris@0: 'settings' => [ Chris@0: 'image_link' => '', Chris@0: 'responsive_image_style' => 'style_one', Chris@0: ], Chris@0: ]; Chris@0: $display = entity_get_display('node', 'article', 'default'); Chris@0: $display->setComponent($field_name, $display_options) Chris@0: ->save(); Chris@0: Chris@0: // View the node. Chris@0: $this->drupalGet('node/' . $nid); Chris@0: Chris@0: // Assert the media attribute is present if it has a value. Chris@0: $large_style = ImageStyle::load('large'); Chris@0: $medium_style = ImageStyle::load('medium'); Chris@0: $node = $node_storage->load($nid); Chris@0: $image_uri = File::load($node->{$field_name}->target_id)->getFileUri(); Chris@0: $this->assertRaw('randomMachineName()); Chris@0: $field_settings = ['alt_field_required' => 0]; Chris@0: $this->createImageField($field_name, 'article', ['uri_scheme' => 'public'], $field_settings); Chris@0: // Create a new node with an image attached. Chris@0: $test_image = current($this->drupalGetTestFiles('image')); Chris@0: Chris@0: // Test the image linked to file formatter. Chris@0: $display_options = [ Chris@0: 'type' => 'responsive_image', Chris@0: 'settings' => [ Chris@0: 'image_link' => $link_type, Chris@0: 'responsive_image_style' => 'style_one', Chris@0: ], Chris@0: ]; Chris@0: entity_get_display('node', 'article', 'default') Chris@0: ->setComponent($field_name, $display_options) Chris@0: ->save(); Chris@0: // Ensure that preview works. Chris@0: $this->previewNodeImage($test_image, $field_name, 'article'); Chris@0: Chris@0: // Look for a picture tag in the preview output Chris@0: $this->assertPattern('/picture/'); Chris@0: Chris@0: $nid = $this->uploadNodeImage($test_image, $field_name, 'article'); Chris@0: $this->container->get('entity.manager')->getStorage('node')->resetCache([$nid]); Chris@0: $node = Node::load($nid); Chris@0: Chris@0: // Use the responsive image formatter linked to file formatter. Chris@0: $display_options = [ Chris@0: 'type' => 'responsive_image', Chris@0: 'settings' => [ Chris@0: 'image_link' => $link_type, Chris@0: 'responsive_image_style' => 'style_one', Chris@0: ], Chris@0: ]; Chris@0: entity_get_display('node', 'article', 'default') Chris@0: ->setComponent($field_name, $display_options) Chris@0: ->save(); Chris@0: Chris@0: // Create a derivative so at least one MIME type will be known. Chris@0: $large_style = ImageStyle::load('large'); Chris@0: $image_uri = File::load($node->{$field_name}->target_id)->getFileUri(); Chris@0: $large_style->createDerivative($image_uri, $large_style->buildUri($image_uri)); Chris@0: Chris@0: // Output should contain all image styles and all breakpoints. Chris@0: $this->drupalGet('node/' . $nid); Chris@0: $this->removeWhiteSpace(); Chris@0: switch ($link_type) { Chris@0: case 'file': Chris@0: // Make sure the link to the file is present. Chris@0: $this->assertPattern('/assertPattern('/url(), '/') . '"(.*?)>