blazy-8.x-2.x-dev/tests/src/FunctionalJavascript/BlazyFilterJavaScriptTest.php
tests/src/FunctionalJavascript/BlazyFilterJavaScriptTest.php
<?php
namespace Drupal\Tests\blazy\FunctionalJavascript;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Render\RenderContext;
use Drupal\FunctionalJavascriptTests\DrupalSelenium2Driver;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\Tests\blazy\Traits\BlazyCreationTestTrait;
use Drupal\Tests\blazy\Traits\BlazyUnitTestTrait;
use Drupal\blazy\Blazy;
use Drupal\filter\Entity\FilterFormat;
use Drupal\filter\FilterPluginCollection;
use Drupal\filter\FilterProcessResult;
/**
* Tests the Blazy Filter JavaScript using Selenium, or Chromedriver.
*
* @group blazy
*/
class BlazyFilterJavaScriptTest extends WebDriverTestBase {
use BlazyUnitTestTrait;
use BlazyCreationTestTrait;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected $minkDefaultDriverClass = DrupalSelenium2Driver::class;
/**
* {@inheritdoc}
*/
protected static $modules = [
'field',
'filter',
'image',
'media',
'node',
'text',
'blazy',
'blazy_test',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->setUpVariables();
$this->root = $this->container->getParameter('app.root');
$this->fileSystem = $this->container->get('file_system');
$this->entityFieldManager = $this->container->get('entity_field.manager');
$this->formatterPluginManager = $this->container->get('plugin.manager.field.formatter');
$this->blazyAdmin = $this->container->get('blazy.admin');
$this->blazyOembed = $this->container->get('blazy.oembed');
$this->blazyManager = $this->container->get('blazy.manager');
$this->testPluginId = 'blazy_filter';
$this->maxParagraphs = 280;
// Create a text format.
$full_html = FilterFormat::create([
'format' => 'full_html',
'name' => 'Full HTML',
'weight' => 0,
]);
$full_html->save();
// Enable the Blazy filter.
$this->filterFormatFull = FilterFormat::load('full_html');
$this->filterFormatFull->setFilterConfig('blazy_filter', [
'status' => TRUE,
'settings' => [
'filter_tags' => [
'img' => 'img',
'iframe' => 'iframe',
],
],
]);
$this->filterFormatFull->save();
$this->setUpRealImage();
}
/**
* Test the Blazy filter has media-wrapper--blazy for IMG and IFRAME elements.
*/
public function testFilterDisplay() {
$image_path = $this->getImagePath(TRUE);
$settings = Blazy::init();
$settings['extra_text'] = $text = $this->dummyText();
$this->setUpContentTypeTest($this->bundle);
$this->setUpContentWithItems($this->bundle, $settings);
$session = $this->getSession();
$this->drupalGet('node/' . $this->entity->id());
// Ensures Blazy is not loaded on page load.
// @todo with Native lazyload, b-loaded is enforced on page load. And
// since the testing browser Chrome support it, it is irrelevant.
// @todo $this->assertSession()->elementNotExists('css', '.b-loaded');
// Capture the initial page load moment.
$this->createScreenshot($image_path . '/1_blazy_filter_initial.png');
$this->assertSession()->elementExists('css', '.b-lazy');
// Trigger Blazy to load images by scrolling down window.
$session->executeScript('window.scrollTo(0, document.body.scrollHeight);');
// Capture the loading moment after scrolling down the window.
$this->createScreenshot($image_path . '/2_blazy_filter_loading.png');
// Verifies that our filter works identified by media-wrapper--blazy class.
$this->assertSession()->elementExists('css', '.media-wrapper--blazy');
$this->assertSession()->elementContains('css', '.media-wrapper--blazy', 'b-lazy');
// Verifies attributes and URIs are cleaned out.
// The problem with raw attributes were discrete behaviors causing failed
// lazy load, nothing related to skiddies businesses. It appears fixed since
// blazy:2.17-beta1+, see #3374519.
// See https://www.drupal.org/node/3129738.
// See https://mink.behat.org/en/latest/guides/traversing-pages.html#css-selector.
$this->assertSession()->elementExists('css', 'img[usemap]');
$this->assertSession()->elementExists('css', 'img[data-onmouseover]');
$this->assertSession()->elementNotExists('css', 'img[onmouseover]');
$this->assertSession()->elementNotExists('css', 'img[alt*=strong]');
$this->assertSession()->elementExists('css', 'img[src^=data]');
$this->assertSession()->elementExists('css', 'img[data-src^=alert]');
$this->assertSession()->elementNotExists('css', 'img[data-src^=javascript]');
$this->assertSession()->elementExists('xpath', '//img[contains(@src, "data:image")]');
$this->assertSession()->elementExists('xpath', '//img[contains(@class, "width-full")]');
// Also verifies that [data-unblazy] should not be touched, nor lazyloaded.
$this->assertSession()->elementNotContains('css', '.media-wrapper--blazy', 'data-unblazy');
// Verifies that one of the images is there once loaded.
// @phpstan-ignore-next-line
$loaded = $this->assertSession()->waitForElement('css', '.b-loaded');
$this->assertNotEmpty($loaded);
// Capture the loaded moment.
// The screenshots are at sites/default/files/simpletest/blazy.
$this->createScreenshot($image_path . '/3_blazy_filter_loaded.png');
// Verifies the library is loaded.
['result' => $result, 'html' => $html] = $this->applyFilter($text);
$this->assertNotSame($html, $text);
$attachments = $result->getAttachments();
$this->assertContains('blazy/filter', $attachments['library']);
$this->assertArrayHasKey('blazy', $attachments['drupalSettings']);
}
/**
* Applies the `@Filter=blazy_fiter` filter to text, pipes to raw content.
*
* @param string $text
* The text string to be filtered.
* @param string $identifier
* The any text which identifies this filter.
* @param string $langcode
* The language code of the text to be filtered.
*
* @return \Drupal\filter\FilterProcessResult
* The filtered text, wrapped in a FilterProcessResult object, and possibly
* with associated assets, cacheability metadata and placeholders.
*/
protected function applyFilter($text, $identifier = 'media-wrapper--blazy', $langcode = 'en') {
$this->assertStringNotContainsString($identifier, $text);
$result = $this->processText($text, $langcode);
$html = $result->getProcessedText();
$this->assertStringContainsString($identifier, $html);
return ['result' => $result, 'html' => $html];
}
/**
* Processes text through the provided filters, taken from media embed.
*
* @param string $text
* The text string to be filtered.
* @param string $langcode
* The language code of the text to be filtered.
* @param string[] $filter_ids
* (optional) The filter plugin IDs to apply to the given text, in the order
* they are being requested to be executed.
*
* @return \Drupal\filter\FilterProcessResult
* The filtered text, wrapped in a FilterProcessResult object, and possibly
* with associated assets, cacheability metadata and placeholders.
*
* @see \Drupal\filter\Element\ProcessedText::preRenderText()
*/
protected function processText($text, $langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED, array $filter_ids = ['blazy_filter']) {
$manager = $this->container->get('plugin.manager.filter');
$bag = new FilterPluginCollection($manager, []);
$filters = [];
foreach ($filter_ids as $filter_id) {
$filters[] = $bag->get($filter_id);
}
$render_context = new RenderContext();
/** @var \Drupal\filter\FilterProcessResult $filter_result */
$filter_result = $this->container->get('renderer')->executeInRenderContext($render_context, function () use ($text, $filters, $langcode) {
$metadata = new BubbleableMetadata();
foreach ($filters as $filter) {
/** @var \Drupal\filter\FilterProcessResult $result */
$result = $filter->process($text, $langcode);
$metadata = $metadata->merge($result);
$text = $result->getProcessedText();
}
return (new FilterProcessResult($text))->merge($metadata);
});
if (!$render_context->isEmpty()) {
$filter_result = $filter_result->merge($render_context->pop());
}
return $filter_result;
}
/**
* Returns a dummy text.
*/
protected function dummyText(): string {
$uuid = $this->dummyItem->uuid();
$text = '<div style="width: 640px;">';
$text .= '<iframe src="https://www.youtube.com/watch?v=uny9kbh4iOEd" width="640" height="360"></iframe>';
$text .= '<img src="' . $this->url . '" width="320" height="320" />';
$text .= '<img src="' . $this->dummyUrl . '" width="320" height="320" data-entity-type="file" data-entity-uuid="' . $uuid . '"/>';
$text .= '<img src="https://www.drupal.org/files/project-images/slick-carousel-drupal.png" width="215" height="162" />';
$text .= '<img data-unblazy src="' . $this->url . '" width="320" height="320" />';
$text .= '<IMG SRC="javascript:alert(\'XSS B\');">';
$text .= '<IMG SRC=javascript:alert(\'XSS C\')>';
$text .= '<IMG SRC=JaVaScRiPt:alert(\'XSS D\')>';
$text .= '<IMG SRC=javascript:alert("XSS E")>';
$text .= '<IMG SRC=`javascript:alert("RSnake says, \'XSS F\'")`>';
$text .= '<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))>';
$text .= '<IMG SRC=# onmouseover="alert(\'xxs G\')">';
$text .= '<IMG SRC= onmouseover="alert(\'xxs H\')">';
$text .= '<IMG onmouseover="alert(\'xxs I\')">';
$text .= '<IMG SRC=/ onerror="alert(String.fromCharCode(88,83,83))"></img>';
$text .= '<IMG SRC=javascript:alert(
'XSS')>';
$text .= '<IMG SRC=javascript:a&
#0000108ert('XSS')>';
$text .= '<IMG SRC=javascript:alert('XSS')>';
$text .= '#"><img src=M onerror=alert(\'XSS R\');>';
$text .= '<IMG SRC="jav ascript:alert(\'XSS J\');">';
$text .= '<IMG SRC="jav	ascript:alert(\'XSS K\');">';
$text .= '<IMG SRC="jav
ascript:alert(\'XSS L\');">';
$text .= '<IMG SRC="jav
ascript:alert(\'XSS M\');">';
$text .= '<IMG SRC="  javascript:alert(\'XSS N\');">';
$text .= '<IMG DYNSRC="javascript:alert(\'XSS O\')">\';';
$text .= '<IMG LOWSRC="javascript:alert(\'XSS P\')">';
$text .= '<IMG SRC=\'vbscript:msgbox("XSS Q")\'>';
$text .= '<IMG SRC="livescript:[code]">';
$text .= '<img onmouseover="JaVaScRiPt:alert(\'XSS D\')" class="width-full" width="900" height="1600" alt="<strong>The dosage probation roadmap, shows 4 phases described in a set of shapes</strong>" src="' . $this->url . '" usemap="#image_map2">
<map name="image_map2">
<area alt="Step 1" href="/node/1" coords="158,224,314,317,315,377,156,469,109,346,0" shape="polygon">
<area alt="Step 2" href="/node/2" coords="377,85,380,268,327,299,168,208,241,100,0" shape="polygon">
</map>';
$text .= '</div>';
return $text;
}
}
