tests/src/Functional/EntityEmbedTestBase.php | 28 +- .../EntityEmbedFilterTest.php | 854 +++++++++++++-------- tests/src/Kernel/EntityEmbedFilterTestBase.php | 140 ++++ .../Kernel/EntityEmbedFilterTranslationTest.php | 164 ++++ 4 files changed, 866 insertions(+), 320 deletions(-) diff --git a/tests/src/Functional/EntityEmbedTestBase.php b/tests/src/Functional/EntityEmbedTestBase.php index c92bcec..287ed02 100644 --- a/tests/src/Functional/EntityEmbedTestBase.php +++ b/tests/src/Functional/EntityEmbedTestBase.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\entity_embed\Functional; +use Drupal\Component\Utility\Html; use Drupal\Core\Entity\EntityInterface; use Drupal\editor\Entity\Editor; use Drupal\file\Entity\File; @@ -17,9 +18,7 @@ abstract class EntityEmbedTestBase extends BrowserTestBase { use TestFileCreationTrait; /** - * Modules to enable. - * - * @var array + * {@inheritdoc} */ protected static $modules = [ 'entity_embed', @@ -102,14 +101,12 @@ abstract class EntityEmbedTestBase extends BrowserTestBase { $settings['type'] = 'page'; $settings['title'] = 'Embed Test Node'; $settings['body'] = ['value' => 'This node is to be used for embedding in other nodes.', 'format' => 'custom_format']; + $settings['uuid'] = 'e7a3e1fe-b69b-417e-8ee4-c80cb7640e63'; $this->node = $this->drupalCreateNode($settings); } /** * Retrieves a sample file of the specified type. - * - * @return \Drupal\file\FileInterface - * The test file created. */ protected function getTestFile($type_name, $size = NULL) { // Get a file to upload. @@ -133,4 +130,23 @@ abstract class EntityEmbedTestBase extends BrowserTestBase { $this->assertEquals([], array_diff($expected_plugins, array_keys($plugin_options)), $message); } + /** + * Get an embed code with given attributes. + * + * @param array $attributes + * The attributes to add. + * + * @return string + * A string containing a drupal-entity dom element. + */ + public function createEmbedCode(array $attributes) { + $dom = Html::load('This placeholder should not be rendered.'); + $xpath = new \DOMXPath($dom); + $drupal_entity = $xpath->query('//drupal-entity')[0]; + foreach ($attributes as $attribute => $value) { + $drupal_entity->setAttribute($attribute, $value); + } + return Html::serialize($dom); + } + } diff --git a/tests/src/Functional/EntityEmbedFilterTest.php b/tests/src/Kernel/EntityEmbedFilterTest.php similarity index 14% rename from tests/src/Functional/EntityEmbedFilterTest.php rename to tests/src/Kernel/EntityEmbedFilterTest.php index 1c9be44..df1910c 100644 --- a/tests/src/Functional/EntityEmbedFilterTest.php +++ b/tests/src/Kernel/EntityEmbedFilterTest.php @@ -1,352 +1,506 @@ installSchema('file', ['file_usage']); + $this->installEntitySchema('file'); + $this->installEntitySchema('media'); + $this->installEntitySchema('date_format'); + $this->installConfig('image'); + $this->installConfig('media'); + $this->installConfig('system'); + } + + /** + * Ensures entities are rendered with correct data attributes. * - * @var \Drupal\entity_embed\Plugin\Filter\EntityEmbedFilter + * @dataProvider providerTestFilter */ - protected $filter; + public function testFilter(array $embed_attributes, array $contains, array $not_contains, array $expected_attributes, $is_published, $allowed_to_view_unpublished) { + $content = $this->createEmbedCode($embed_attributes); + + $entity = $this->loadEntityByAttributes($embed_attributes); + $entity->setPublished($is_published)->save(); + + $permissions = [ + 'access content', + ]; + if ($allowed_to_view_unpublished) { + $permissions[] = 'view own unpublished content'; + } + $this->container->get('current_user') + ->addRole($this->drupalCreateRole($permissions)); + + /** @var \Drupal\filter\FilterProcessResult $filter_result */ + $filter_result = $this->filter->process($content, 'en'); + $output = $filter_result->getProcessedText(); + + if (!$is_published && !$allowed_to_view_unpublished) { + $this->assertEmpty($output); + return; + } + $this->assertNotContains('drupal-entity data-entity-type="node" data-entity', $output); + $this->assertEntityEmbedFilterHasRun($output); + + $this->assertContainsMultiple($contains, $output); + $this->assertNotContainsMultiple($not_contains, $output); + + $dom = Html::load($output); + $xpath = new \DOMXPath($dom); + $rendered_embed = $xpath->query('//div[contains(@class, "embedded-entity")]')[0]; + $this->assertNotEmpty($rendered_embed); + $this->assertHasAttributes($rendered_embed, $expected_attributes); + if (empty($embed_attributes['data-entity-id'])) { + $this->assertFalse($rendered_embed->hasAttribute('data-entity-id')); + } + } /** - * {@inheritdoc} + * Data provider for testFilter. */ - protected function setUp() { - parent::setUp(); + public function providerTestFilter() { + return [ + 'using entity ID and view mode' => [ + 'embed_attributes' => [ + 'data-entity-type' => 'node', + 'data-entity-id' => 1, + 'data-view-mode' => 'teaser', + ], + 'contains' => [ + 'This node is to be used for embedding in other nodes.', + ], + 'not_contains' => [], + 'expected_attributes' => [ + 'data-entity-type' => 'node', + 'data-entity-id' => 1, + 'data-view-mode' => 'teaser', + 'data-entity-uuid' => 'e7a3e1fe-b69b-417e-8ee4-c80cb7640e63', + 'data-langcode' => 'en', + 'data-entity-embed-display' => 'entity_reference:entity_reference_entity_view', + 'data-entity-embed-display-settings' => 'teaser', + ], + 'published' => TRUE, + 'view_unpublished' => FALSE, + ], + 'embedded entity is not rendered if unpublished' => [ + 'embed_attributes' => [ + 'data-entity-type' => 'node', + 'data-entity-id' => 1, + 'data-view-mode' => 'teaser', + ], + 'contains' => [], + 'not_contains' => [], + 'expected_attributes' => [], + 'published' => FALSE, + 'view_unpublished' => FALSE, + ], + 'Unpublished and current user has view unpublished permission' => [ + 'embed_attributes' => [ + 'data-entity-type' => 'node', + 'data-entity-id' => 1, + 'data-view-mode' => 'teaser', + ], + 'contains' => ['This node is to be used for embedding in other nodes.'], + 'not_contains' => [], + 'expected_attributes' => [ + 'data-entity-type' => 'node', + 'data-entity-id' => 1, + 'data-view-mode' => 'teaser', + 'data-entity-uuid' => 'e7a3e1fe-b69b-417e-8ee4-c80cb7640e63', + 'data-langcode' => 'en', + 'data-entity-embed-display' => 'entity_reference:entity_reference_entity_view', + 'data-entity-embed-display-settings' => 'teaser', + ], + 'published' => FALSE, + 'view_unpublished' => TRUE, + ], + 'using entity UUID and view mode' => [ + 'embed_attributes' => [ + 'data-entity-type' => 'node', + 'data-entity-uuid' => 'e7a3e1fe-b69b-417e-8ee4-c80cb7640e63', + 'data-view-mode' => 'teaser', + ], + 'contains' => [ + 'This node is to be used for embedding in other nodes.', + ], + 'not_contains' => [], + 'expected_attributes' => [ + 'data-entity-type' => 'node', + 'data-view-mode' => 'teaser', + 'data-entity-uuid' => 'e7a3e1fe-b69b-417e-8ee4-c80cb7640e63', + 'data-langcode' => 'en', + 'data-entity-embed-display' => 'entity_reference:entity_reference_entity_view', + 'data-entity-embed-display-settings' => 'teaser', + ], + 'published' => TRUE, + 'view_unpublished' => FALSE, + ], + 'Ensure that UUID is preferred over ID when both attributes are present' => [ + 'embed_attributes' => [ + 'data-entity-type' => 'node', + 'data-entity-id' => 2, + 'data-entity-uuid' => 'e7a3e1fe-b69b-417e-8ee4-c80cb7640e63', + 'data-view-mode' => 'teaser', + ], + 'contains' => [ + 'This node is to be used for embedding in other nodes.', + ], + 'not_contains' => [], + 'expected_attributes' => [ + 'data-entity-type' => 'node', + 'data-entity-id' => 2, + 'data-entity-uuid' => 'e7a3e1fe-b69b-417e-8ee4-c80cb7640e63', + 'data-view-mode' => 'teaser', + 'data-langcode' => 'en', + 'data-entity-embed-display' => 'entity_reference:entity_reference_entity_view', + 'data-entity-embed-display-settings' => 'teaser', + ], + 'published' => TRUE, + 'view_unpublished' => FALSE, + ], + 'Test deprecated default Entity Embed Display plugin.' => [ + 'embed_attributes' => [ + 'data-entity-type' => 'node', + 'data-entity-uuid' => 'e7a3e1fe-b69b-417e-8ee4-c80cb7640e63', + 'data-entity-embed-display' => 'default', + 'data-entity-embed-display-settings' => '{"view_mode":"teaser"}', + ], + 'contains' => [ + 'This node is to be used for embedding in other nodes.', + ], + 'not_contains' => [], + 'expected_attributes' => [ + 'data-entity-type' => 'node', + 'data-entity-uuid' => 'e7a3e1fe-b69b-417e-8ee4-c80cb7640e63', + 'data-entity-embed-display' => 'entity_reference:entity_reference_entity_view', + 'data-entity-embed-display-settings' => 'teaser', + 'data-langcode' => 'en', + ], + 'published' => TRUE, + 'view_unpublished' => FALSE, + ], + 'Ensure that Entity Embed Display plugin is preferred over view mode when both are present' => [ + 'embed_attributes' => [ + 'data-entity-type' => 'node', + 'data-entity-uuid' => 'e7a3e1fe-b69b-417e-8ee4-c80cb7640e63', + 'data-entity-embed-display' => 'default', + 'data-entity-embed-display-settings' => '{"view_mode":"full"}', + 'data-view-mode' => 'some-invalid-view-mode', + ], + 'contains' => [ + 'This node is to be used for embedding in other nodes.', + ], + 'not_contains' => [], + 'expected_attributes' => [ + 'data-entity-embed-display' => 'entity_reference:entity_reference_entity_view', + 'data-entity-embed-display-settings' => 'full', + 'data-entity-type' => 'node', + 'data-entity-uuid' => 'e7a3e1fe-b69b-417e-8ee4-c80cb7640e63', + 'data-view-mode' => 'some-invalid-view-mode', + 'data-langcode' => 'en', + ], + 'published' => TRUE, + 'view_unpublished' => FALSE, + ], + 'Ensure that custom attributes are retained' => [ + 'embed_attributes' => [ + 'data-foo' => 'bar', + 'foo' => 'bar', + 'data-entity-type' => 'node', + 'data-entity-uuid' => 'e7a3e1fe-b69b-417e-8ee4-c80cb7640e63', + 'data-view-mode' => 'teaser', + ], + 'contains' => [ + 'This node is to be used for embedding in other nodes.', + ], + 'not_contains' => [], + 'expected_attributes' => [ + 'data-foo' => 'bar', + 'foo' => 'bar', + 'data-entity-type' => 'node', + 'data-entity-uuid' => 'e7a3e1fe-b69b-417e-8ee4-c80cb7640e63', + 'data-view-mode' => 'teaser', + 'data-langcode' => 'en', + 'data-entity-embed-display' => 'entity_reference:entity_reference_entity_view', + 'data-entity-embed-display-settings' => 'teaser', + ], + 'published' => TRUE, + 'view_unpublished' => FALSE, + ], - $manager = $this->container->get('plugin.manager.filter'); - $bag = new FilterPluginCollection($manager, []); - $this->filter = $bag->get('entity_embed'); + ]; } /** - * Tests the entity_embed filter. + * Tests for invalid Entity. * - * Ensures that entities are getting rendered when correct data attributes - * are passed. Also tests situations when embed fails. + * @dataProvider providerTestInvalidEntity */ - public function testFilter() { - $assert_session = $this->assertSession(); - - // Tests entity embed using entity ID and view mode. - $content = 'This placeholder should not be rendered.'; - $settings = []; - $settings['type'] = 'page'; - $settings['title'] = 'Test entity embed with entity-id and view-mode'; - $settings['body'] = [['value' => $content, 'format' => 'custom_format']]; - $node = $this->drupalCreateNode($settings); - $this->drupalGet('node/' . $node->id()); - $this->assertSession()->responseNotContains('assertSession()->responseContains($this->node->body->value); - $this->assertSession()->responseNotContains('This placeholder should not be rendered.'); - $this->assertSession()->responseContains('
'); - - // Tests that embedded entity is not rendered if not accessible. - $this->node->setPublished(FALSE)->save(); - $settings = []; - $settings['type'] = 'page'; - $settings['title'] = 'Test un-accessible entity embed with entity-id and view-mode'; - $settings['body'] = [['value' => $content, 'format' => 'custom_format']]; - $node = $this->drupalCreateNode($settings); - $this->drupalGet('node/' . $node->id()); - $this->assertSession()->responseNotContains('assertSession()->responseNotContains($this->node->body->value); - // Verify placeholder does not appear in the output when embed is - // successful. - $this->assertSession()->responseNotContains(strip_tags($content)); - // Tests that embedded entity is displayed to the user who has - // the view unpublished content permission. - $this->createRole(['view own unpublished content'], 'access_unpublished'); - $this->webUser->addRole('access_unpublished'); - $this->webUser->save(); - $this->drupalGet('node/' . $node->id()); - $this->assertSession()->responseNotContains('assertSession()->responseContains($this->node->body->value); - $this->assertSession()->responseNotContains('This placeholder should not be rendered.'); - $this->assertSession()->responseContains('
'); - $this->webUser->removeRole('access_unpublished'); - $this->webUser->save(); - $this->node->setPublished(TRUE)->save(); - - // Tests entity embed using entity UUID and view mode. - $content = 'This placeholder should not be rendered.'; - $settings = []; - $settings['type'] = 'page'; - $settings['title'] = 'Test entity embed with entity-uuid and view-mode'; - $settings['body'] = [['value' => $content, 'format' => 'custom_format']]; - $node = $this->drupalCreateNode($settings); - $this->drupalGet('node/' . $node->id()); - $this->assertSession()->responseNotContains('assertSession()->responseContains($this->node->body->value); - $this->assertSession()->responseNotContains('This placeholder should not be rendered.'); - $this->assertSession()->responseContains('
'); - $this->assertSession()->responseHeaderContains('X-Drupal-Cache-Tags', 'foo:' . $this->node->id()); - - // Ensure that placeholder is not replaced when embed is unsuccessful. - $content = 'This placeholder should be rendered since specified entity does not exists.'; - $settings = []; - $settings['type'] = 'page'; - $settings['title'] = 'Test that placeholder is retained when specified entity does not exists'; - $settings['body'] = [['value' => $content, 'format' => 'custom_format']]; - $node = $this->drupalCreateNode($settings); - $this->drupalGet('node/' . $node->id()); - $this->assertSession()->responseNotContains('assertSession()->responseNotContains('This placeholder should not be rendered.'); - - // Ensure that UUID is preferred over ID when both attributes are present. - $sample_node = $this->drupalCreateNode(); - $content = 'This placeholder should not be rendered.'; - $settings = []; - $settings['type'] = 'page'; - $settings['title'] = 'Test that entity-uuid is preferred over entity-id when both attributes are present'; - $settings['body'] = [['value' => $content, 'format' => 'custom_format']]; - $node = $this->drupalCreateNode($settings); - $this->drupalGet('node/' . $node->id()); - $this->assertSession()->responseNotContains('assertSession()->responseContains($this->node->body->value); - $this->assertSession()->responseNotContains($sample_node->body->value); - $this->assertSession()->responseNotContains('This placeholder should not be rendered.'); - $this->assertSession()->responseContains('
'); - - // Test deprecated 'default' Entity Embed Display plugin. - $content = 'This placeholder should not be rendered.'; - $settings = []; - $settings['type'] = 'page'; - $settings['title'] = 'Test entity embed with entity-embed-display and data-entity-embed-display-settings'; - $settings['body'] = [['value' => $content, 'format' => 'custom_format']]; - $node = $this->drupalCreateNode($settings); - $this->drupalGet('node/' . $node->id()); - // Verify that the embedded node exists in page. - $this->assertSession()->responseContains($this->node->body->value); - $this->assertSession()->responseNotContains('This placeholder should not be rendered.'); - $this->assertSession()->responseContains('
'); - - // Ensure that Entity Embed Display plugin is preferred over view mode when - // both attributes are present. - $content = 'This placeholder should not be rendered.'; - $settings = []; - $settings['type'] = 'page'; - $settings['title'] = 'Test entity embed with entity-embed-display and data-entity-embed-display-settings'; - $settings['body'] = [['value' => $content, 'format' => 'custom_format']]; - $node = $this->drupalCreateNode($settings); - $this->drupalGet('node/' . $node->id()); - // Verify embedded node exists in page with the view mode specified - // by entity-embed-settings. - $this->assertSession()->responseContains($this->node->body->value); - $this->assertSession()->responseNotContains('This placeholder should not be rendered.'); - $this->assertSession()->elementExists('css', 'figure.caption-drupal-entity.align-left div.embedded-entity[data-entity-embed-display="entity_reference:entity_reference_entity_view"][data-entity-embed-display-settings="full"][data-entity-type="node"][data-entity-uuid="' . $this->node->uuid() . '"][data-view-mode="some-invalid-view-mode"][data-langcode="en"]'); - $this->assertSession()->elementTextContains('css', 'figure.caption-drupal-entity.align-left figcaption', 'test caption'); - - // Ensure the embedded node doesn't contain data tags on the full page. - $this->drupalGet('node/' . $this->node->id()); - $this->assertSession()->responseNotContains('data-align="left"'); - $this->assertSession()->responseNotContains('data-caption="test caption"'); - - // Test that tag of container element is not replaced when it's not - // . - $content = 'this placeholder should not be rendered.'; - $settings = []; - $settings['type'] = 'page'; - $settings['title'] = 'test entity embed with entity-id and view-mode'; - $settings['body'] = [['value' => $content, 'format' => 'custom_format']]; - $node = $this->drupalCreateNode($settings); - $this->drupalget('node/' . $node->id()); - $this->assertSession()->responseNotContains($this->node->body->value); - $this->assertSession()->responseContains(''); - $content = '
this placeholder should not be rendered.
'; - $settings = []; - $settings['type'] = 'page'; - $settings['title'] = 'test entity embed with entity-id and view-mode'; - $settings['body'] = [['value' => $content, 'format' => 'custom_format']]; - $node = $this->drupalCreateNode($settings); - $this->drupalget('node/' . $node->id()); - $this->assertSession()->responseNotContains($this->node->body->value); - $this->assertSession()->responseContains('
createEmbedCode($embed_attributes); + + /** @var \Drupal\filter\FilterProcessResult $filter_result */ + $filter_result = $this->filter->process($content, 'en'); + $output = $filter_result->getProcessedText(); + $this->assertEntityEmbedFilterHasRun($output); + + $this->assertContainsMultiple($contains, $output); + $this->assertNotContainsMultiple($not_contains, $output); + } + + /** + * Data provider for testInvalidEntity. + * + * @TODO: add more tests. + */ + public function providerTestInvalidEntity() { + return [ + "Ensure that content that can't be loaded is replaced with warning" => [ + 'embed_attributes' => [ + 'data-entity-type' => 'node', + 'data-entity-id' => 'InvalidID', + 'data-view-mode' => 'teaser', + ], + 'contains' => [], + 'not_contains' => [ + 'node->id(); + $content = $this->createEmbedCode([ + 'data-entity-type' => 'node', + 'data-entity-id' => $node_id, + 'data-view-mode' => 'teaser', + ]); + + $random_tag = 'random-tag-name'; + $content = str_replace('drupal-entity', $random_tag, $content); + + $filter_result = $this->filter->process($content, 'en'); + $output = $filter_result->getProcessedText(); + $this->assertNotContains($this->node->body->value, $output); + + $dom = Html::load($output); + $xpath = new \DOMXPath($dom); + $untouched = $xpath->query("//{$random_tag}")[0]; + $this->assertNotEmpty($untouched); + $this->assertHasAttributes($untouched, [ + 'data-entity-type' => 'node', + 'data-entity-id' => $node_id, + 'data-view-mode' => 'teaser', + ]); + } + + /** + * Tests for figure caption. + * + * A lot of tests include figcaption assertions or queries. But I think we + * can split this up better with dedicated figcaption tests. + * + * @dataProvider providerTestFigCaption + */ + public function testFigCaption() { + + } + + /** + * Data provider for testFigCaption. + */ + public function providerTestFigCaption() { + return []; + } + + /** + * Tests that data-entity-embed-display is preferred over data-view-mode. + */ + public function testEntityEmbedDisplayAttributeVersusViewModeAttribute() { + $content = $this->createEmbedCode([ + 'data-entity-type' => 'node', + 'data-entity-uuid' => 'e7a3e1fe-b69b-417e-8ee4-c80cb7640e63', + 'data-entity-embed-display' => 'default', + 'data-entity-embed-display-settings' => '{"view_mode":"full"}', + 'data-view-mode' => 'some-invalid-view-mode', + ]); + + /** @var \Drupal\filter\FilterProcessResult $filter_result */ + $filter_result = $this->filter->process($content, 'en'); + $output = $filter_result->getProcessedText(); + $this->assertContains($this->node->body->value, $output); + $this->assertEntityEmbedFilterHasRun($output); + } + + /** + * Test that attributes are correctly added when image formatter is used. + */ + public function testAttributesWhenUsingImageFormatter() { /** @var \Drupal\file\FileInterface $image */ $image = $this->getTestFile('image'); $image->setPermanent(); $image->save(); - $content = 'This placeholder should not be rendered.'; - $settings = []; - $settings['type'] = 'page'; - $settings['title'] = 'test entity image formatter'; - $settings['body'] = [['value' => $content, 'format' => 'custom_format']]; - $node = $this->drupalCreateNode($settings); - $this->drupalget('node/' . $node->id()); - $this->assertSession()->elementExists('css', 'figure.caption-drupal-entity.align-left div.embedded-entity[alt="This is alt text"][data-entity-embed-display="image:image"][data-entity-type="file"][data-entity-uuid="' . $image->uuid() . '"][title="This is title text"][data-langcode="en"] img[src][alt="This is alt text"][title="This is title text"]'); - $this->assertSession()->elementTextContains('css', 'figure.caption-drupal-entity.align-left figcaption', 'test caption'); - - // data-entity-embed-settings is replaced with - // data-entity-embed-display-settings. Check to see if - // data-entity-embed-settings is still working. - $content = 'This placeholder should not be rendered.'; - $settings = []; - $settings['type'] = 'page'; - $settings['title'] = 'Test entity embed with data-entity-embed-settings'; - $settings['body'] = [['value' => $content, 'format' => 'custom_format']]; - $node = $this->drupalCreateNode($settings); - $this->drupalGet('node/' . $node->id()); - $this->assertSession()->elementExists('css', 'figure.caption-drupal-entity.align-left div.embedded-entity[data-entity-embed-display="entity_reference:entity_reference_label"][data-entity-type="node"][data-entity-uuid="' . $this->node->uuid() . '"][data-langcode="en"]'); - $this->assertSession()->elementTextContains('css', 'figure.caption-drupal-entity.align-left div.embedded-entity', 'Embed Test Node'); - $this->assertSession()->elementTextContains('css', 'figure.caption-drupal-entity.align-left figcaption', 'test caption'); - - // Tests entity embed using custom attribute and custom data- attribute. - $content = 'This placeholder should not be rendered.'; - $settings = []; - $settings['type'] = 'page'; - $settings['title'] = 'Test entity embed with custom attributes'; - $settings['body'] = [['value' => $content, 'format' => 'custom_format']]; - $node = $this->drupalCreateNode($settings); - $this->drupalGet('node/' . $node->id()); - $this->assertSession()->responseContains('
'); - - // Tests the placeholder for missing entities. - $embedded_node = $this->drupalCreateNode([ - 'type' => 'page', - 'title' => 'Embedded node', - 'body' => [['value' => 'Embedded text content', 'format' => 'custom_format']], + $content = $this->createEmbedCode([ + 'data-entity-type' => 'file', + 'data-entity-uuid' => $image->uuid(), + 'data-entity-embed-display' => 'image:image', + 'data-entity-embed-display-settings' => '{"image_style":"","image_link":""}', + 'data-align' => 'left', + 'data-caption' => 'test caption', + 'alt' => 'This is alt text', + 'title' => 'This is title text', + ]); + + /** @var \Drupal\filter\FilterProcessResult $filter_result */ + $filter_result = $this->filter->process($content, 'en'); + $output = $filter_result->getProcessedText(); + $this->assertEntityEmbedFilterHasRun($output); + + $dom = Html::load($output); + $xpath = new \DOMXPath($dom); + /** @var \DOMNode $rendered_embed */ + $rendered_embed = $xpath->query('//div[contains(@class, "embedded-entity")]')[0]; + $this->assertHasAttributes($rendered_embed, [ + 'alt' => 'This is alt text', + 'data-align' => 'left', + 'data-entity-embed-display' => 'image:image', + 'data-entity-type' => 'file', + 'data-entity-uuid' => $image->uuid(), + 'title' => 'This is title text', + 'data-langcode' => 'en', + ]); + $img = $xpath->query('//div[contains(@class, "embedded-entity")]/img')[0]; + $this->assertHasAttributes($img, [ + 'alt' => 'This is alt text', + 'title' => 'This is title text', ]); - $content = ''; - $settings = []; - $settings['type'] = 'page'; - $settings['title'] = 'Host node'; - $settings['body'] = [['value' => $content, 'format' => 'custom_format']]; - $node = $this->drupalCreateNode($settings); - $this->drupalGet('node/' . $node->id()); - $assert_session->pageTextContains('Embedded text content'); - $assert_session->elementNotExists('css', 'img[alt^="Deleted content encountered, site owner alerted"]'); - $embedded_node->delete(); - $this->drupalGet('node/' . $node->id()); - $assert_session->pageTextNotContains('Embedded text content'); - $placeholder = $assert_session->elementExists('css', 'img[alt^="Deleted content encountered, site owner alerted"]'); - $this->assertTrue(strpos($placeholder->getAttribute('src'), 'core/modules/media/images/icons/no-thumbnail.png') > 0); } /** - * Tests the filter in different translation contexts. + * Tests BC for`data-entity-embed-display-settings`'s predecessor. + * + * @group legacy */ - public function testTranslation() { - $content = 'This placeholder should not be rendered.'; + public function testEntityEmbedSettingsBackwardsCompatibility() { + $content = $this->createEmbedCode([ + 'data-entity-type' => 'node', + 'data-entity-uuid' => 'e7a3e1fe-b69b-417e-8ee4-c80cb7640e63', + 'data-entity-embed-display' => 'entity_reference:entity_reference_label', + 'data-entity-embed-settings' => '{"link":"0"}', + ]); + + /** @var \Drupal\filter\FilterProcessResult $filter_result */ + $filter_result = $this->filter->process($content, 'en'); + $output = $filter_result->getProcessedText(); + $this->assertEntityEmbedFilterHasRun($output); + + $dom = Html::load($output); + $xpath = new \DOMXPath($dom); + /** @var \DOMNode $rendered_embed */ + $this->assertSame(0, $xpath->query('//div[contains(@class, "embedded-entity")]/a')->length); + $this->assertSame($this->node->label(), $xpath->query('//div[contains(@class, "embedded-entity")]')[0]->textContent); + } - ConfigurableLanguage::createFromLangcode('pt-br')->save(); - $host_entity = $this->drupalCreateNode([ + /** + * Tests the placeholder for missing entities. + */ + public function testMissingEntityPlaceholder() { + $embedded_node = $this->drupalCreateNode([ 'type' => 'page', - 'body' => [ - 'value' => $content, - 'format' => 'custom_format', - ], + 'title' => 'Embedded node', + 'body' => [['value' => 'Embedded text content']], + ]); + $content = $this->createEmbedCode([ + 'data-entity-type' => 'node', + 'data-entity-uuid' => $embedded_node->uuid(), + 'data-view-mode' => 'default', + ]); + $filter_result = $this->filter->process($content, 'en'); + $output = $filter_result->getProcessedText(); + $this->assertContains('Embedded text content', $output); + $this->assertNotContains('Deleted content encountered, site owner alerted', $output); + + $embedded_node->delete(); + $filter_result = $this->filter->process($content, 'en'); + $output = $filter_result->getProcessedText(); + $this->assertNotContains('Embedded text content', $output); + $dom = Html::load($output); + $xpath = new \DOMXPath($dom); + $deleted_embed_warning = $xpath->query('//img')[0]; + $this->assertNotEmpty($deleted_embed_warning); + $this->assertContains('no-thumbnail.png', $deleted_embed_warning->getAttribute('src')); + $this->assertHasAttributes($deleted_embed_warning, [ + 'alt' => 'Deleted content encountered, site owner alerted.', + 'title' => 'Deleted content.', ]); - $this->drupalGet($host_entity->toUrl()); - $this->assertSession()->statusCodeEquals(200); - $this->assertSession()->pageTextContains($host_entity->getTitle()); - $this->assertSession()->pageTextContains($this->node->getTitle()); - - // Translate the host entity, but keep the same body; only change the title. - $translated_host_entity = $host_entity->addTranslation('pt-br') - ->getTranslation('pt-br') - ->setTitle('Em portugues') - ->set('body', $host_entity->get('body')->getValue()); - $translated_host_entity->save(); - $this->drupalGet('/pt-br/node/' . $host_entity->id()); - $this->assertSession()->statusCodeEquals(200); - $this->assertSession()->pageTextContains($translated_host_entity->getTitle()); - // The embedded node does not have a Portuguese translation, so it should - // display in English. - $this->assertSession()->pageTextContains($this->node->getTitle()); - - // Translate the embedded entity to the same language as the host entity. - $this->node = Node::load($this->node->id()); - $this->node->addTranslation('pt-br') - ->getTranslation('pt-br') - ->setTitle('Embed em portugues') - ->save(); - $this->getSession()->reload(); - $this->assertSession()->statusCodeEquals(200); - $this->assertSession()->pageTextContains($translated_host_entity->getTitle()); - // The translated host entity now should show the matching translation of - // the embedded entity. - $this->assertSession()->pageTextContains($this->node->getTranslation('pt-br')->getTitle()); - - // Change the translated host entity to explicitly embed the untranslated - // entity. - $translated_host_entity->body->value = 'This placeholder should not be rendered.'; - $translated_host_entity->save(); - $this->getSession()->reload(); - $this->assertSession()->statusCodeEquals(200); - $this->assertSession()->pageTextContains($translated_host_entity->getTitle()); - $this->assertSession()->pageTextContains($this->node->getTitle()); - - // Change the untranslated host entity to explicitly embed the Portuguese - // translation of the embedded entity. - $host_entity->body->value = 'This placeholder should not be rendered.'; - $host_entity->save(); - $this->drupalGet('/node/' . $host_entity->id()); - $this->assertSession()->statusCodeEquals(200); - $this->assertSession()->pageTextContains($host_entity->getTitle()); - $this->assertSession()->pageTextContains($this->node->getTranslation('pt-br')->getTitle()); - - // Change the untranslated host entity to explicitly embed a non-existing - // translation of the embedded entity; this should fall back to the default - // translation. - $host_entity->body->value = 'This placeholder should not be rendered.'; - $host_entity->save(); - $this->getSession()->reload(); - $this->assertSession()->statusCodeEquals(200); - $this->assertSession()->pageTextContains($host_entity->getTitle()); - $this->assertSession()->pageTextContains($this->node->getTitle()); - - // Change the translated host entity to explicitly embed a non-existing - // translation of the embedded entity; this should fall back to the default - // translation. - $translated_host_entity->body->value = 'This placeholder should not be rendered.'; - $translated_host_entity->save(); - $this->drupalGet('/pt-br/node/' . $host_entity->id()); - $this->assertSession()->statusCodeEquals(200); - $this->assertSession()->pageTextContains($translated_host_entity->getTitle()); - $this->assertSession()->pageTextContains($this->node->getTitle()); } /** * Tests that embeds are not render cached, and can have per-embed overrides. */ public function testOverridesAndRenderCaching() { + $this->createMediaType('image', ['id' => 'image']); + // The `alt` field property is enabled by default, the `title` one is not. + // Since we want to test it, enable it. + $source_field = FieldConfig::load('media.image.field_media_image'); + $source_field->setSetting('title_field', TRUE); + $source_field->save(); + $this->container->get('current_user') + ->addRole($this->drupalCreateRole(['view media'])); + \Drupal::service('file_system')->copy(\Drupal::root() . '/core/misc/druplicon.png', 'public://batfish.jpg'); /** @var \Drupal\file\FileInterface $file */ $file = File::create([ 'uri' => 'public://batfish.jpg', - 'uid' => $this->webUser->id(), + 'uid' => 2, ]); $file->save(); @@ -368,12 +522,11 @@ class EntityEmbedFilterTest extends EntityEmbedTestBase { ]); $media->save(); - // Test the same embed with different alt and title text. $input = $this->createEmbedCode([ 'alt' => 'alt 1', 'title' => 'title 1', 'data-embed-button' => 'test_media_entity_embed', - 'data-entity-embed-display' => 'view_mode:media.embed', + 'data-entity-embed-display' => 'view_mode:media.full', 'data-entity-embed-display-settings' => '', 'data-entity-type' => 'media', 'data-entity-uuid' => $media->uuid(), @@ -383,7 +536,7 @@ class EntityEmbedFilterTest extends EntityEmbedTestBase { 'alt' => 'alt 2', 'title' => 'title 2', 'data-embed-button' => 'test_media_entity_embed', - 'data-entity-embed-display' => 'view_mode:media.embed', + 'data-entity-embed-display' => 'view_mode:media.full', 'data-entity-embed-display-settings' => '', 'data-entity-type' => 'media', 'data-entity-uuid' => $media->uuid(), @@ -393,7 +546,7 @@ class EntityEmbedFilterTest extends EntityEmbedTestBase { 'alt' => 'alt 3', 'title' => 'title 3', 'data-embed-button' => 'test_media_entity_embed', - 'data-entity-embed-display' => 'view_mode:media.embed', + 'data-entity-embed-display' => 'view_mode:media.full', 'data-entity-embed-display-settings' => '', 'data-entity-type' => 'media', 'data-entity-uuid' => $media->uuid(), @@ -404,7 +557,7 @@ class EntityEmbedFilterTest extends EntityEmbedTestBase { $filter_result = $this->filter->process($input, 'en'); $output = $filter_result->getProcessedText(); $this->assertNotContains('drupal-entity data-entity-type="media" data-entity', $output); - $this->assertNotContains('This placeholder should not be rendered.', $output); + $this->assertEntityEmbedFilterHasRun($output); $dom = Html::load($output); $xpath = new \DOMXPath($dom); @@ -431,32 +584,43 @@ class EntityEmbedFilterTest extends EntityEmbedTestBase { } /** - * Creates an embed code with the given attributes. + * Loads an entity (in the appropriate translation) given HTML attributes. * - * @param array $attributes - * The attributes to set. + * @param string[] $attributes + * An array of HTML attributes, including at least `data-entity-type` and + * `data-entity-uuid`, and optionally `data-langcode`. * - * @return string - * A HTML string containing a tag with the given attributes. + * @return \Drupal\Core\Entity\EntityInterface|null + * The requested entity, or NULL. */ - protected function createEmbedCode(array $attributes) { - $dom = Html::load('This placeholder should not be rendered.'); - $xpath = new \DOMXPath($dom); - $drupal_entity = $xpath->query('//drupal-entity')[0]; - foreach ($attributes as $attribute => $value) { - $drupal_entity->setAttribute($attribute, $value); + protected function loadEntityByAttributes(array $attributes) { + if (!empty($attributes['data-entity-uuid'])) { + $entity = $this->container->get('entity_type.manager') + ->getStorage($attributes['data-entity-type']) + ->loadByProperties(['uuid' => $attributes['data-entity-uuid']]); + $entity = current($entity); + } + elseif (!empty($attributes['data-entity-id'])) { + $entity = $this->container->get('entity_type.manager') + ->getStorage($attributes['data-entity-type']) + ->load($attributes['data-entity-id']); + } + if ($entity && $entity instanceof TranslatableInterface && !empty($attributes['data-langcode'])) { + if ($entity->hasTranslation($attributes['data-langcode'])) { + $entity = $entity->getTranslation($attributes['data-langcode']); + } } - return Html::serialize($dom); + + return $entity; } /** - * Asserts that the DOMNode object has the given attributes. + * Assert that the DOMNode object has the given attributes. * * @param \DOMNode $node * The DOMNode object to check. * @param array $attributes - * An array with attribute names as keys and expected attribute values as - * values. + * An array of attributes. */ protected function assertHasAttributes(\DOMNode $node, array $attributes) { foreach ($attributes as $attribute => $value) { @@ -464,4 +628,66 @@ class EntityEmbedFilterTest extends EntityEmbedTestBase { } } + /** + * Process text containing html for elements containing data-caption. + * + * @param string $input + * String containing HTML. + * @param string $langcode + * The langcode to use. + * + * @return \Drupal\filter\FilterProcessResult + * The processed text with caption. + */ + protected function processFilterCaption($input, $langcode = 'en') { + $filter = $this->filterCaption; + return $this->renderer->executeInRenderContext(new RenderContext(), function () use ($input, $filter, $langcode) { + return $filter->process($input, $langcode); + }); + } + + /** + * Asserts that a haystack contains multiple needles. + * + * @param array $needles + * An array of strings to verify the output contains. + * @param mixed $haystack + * The output to check. + */ + protected function assertContainsMultiple(array $needles, $haystack) { + foreach ($needles as $needle) { + $this->assertContains($needle, $haystack); + } + } + + /** + * Asserts that a haystack does not contain multiple needles. + * + * @param array $needles + * An array of strings to verify the output does not contain. + * @param mixed $haystack + * The output to check. + */ + protected function assertNotContainsMultiple(array $needles, $haystack) { + foreach ($needles as $needle) { + $this->assertNotContains($needle, $haystack); + } + } + + /** + * Retrieves a sample file of the specified type. + */ + protected function getTestFile($type_name, $size = NULL) { + // Get a file to upload. + $file = current($this->getTestFiles($type_name, $size)); + + // Add a filesize property to files as would be read by + // \Drupal\file\Entity\File::load(). + $file->filesize = filesize($file->uri); + + $file = File::create((array) $file); + $file->save(); + return $file; + } + } diff --git a/tests/src/Kernel/EntityEmbedFilterTestBase.php b/tests/src/Kernel/EntityEmbedFilterTestBase.php new file mode 100644 index 0000000..6e2d628 --- /dev/null +++ b/tests/src/Kernel/EntityEmbedFilterTestBase.php @@ -0,0 +1,140 @@ +installSchema('node', 'node_access'); + $this->installSchema('system', 'sequences'); + $this->installEntitySchema('node'); + $this->installEntitySchema('user'); + $this->installConfig('filter'); + $this->installConfig('node'); + + // Create a user with required permissions. Ensure that we don't use user 1 + // because that user is treated in special ways by access control handlers. + $admin_user = $this->drupalCreateUser([]); + $user = $this->drupalCreateUser([ + 'access content', + ]); + $this->container->set('current_user', $user); + + // Create a sample node to be embedded. + $this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']); + $this->node = $this->drupalCreateNode([ + 'title' => 'Embed Test Node', + 'body' => [ + 'value' => 'This node is to be used for embedding in other nodes.', + ], + 'uuid' => 'e7a3e1fe-b69b-417e-8ee4-c80cb7640e63', + ]); + + $this->renderer = $this->container->get('renderer'); + $manager = $this->container->get('plugin.manager.filter'); + $bag = new FilterPluginCollection($manager, []); + $this->filter = $bag->get('entity_embed'); + $this->filterCaption = $bag->get('filter_caption'); + } + + /** + * Gets an embed code with given attributes. + * + * @param array $attributes + * The attributes to add. + * + * @return string + * A string containing a drupal-entity dom element. + * + * @see assertEntityEmbedFilterHasRun() + */ + protected function createEmbedCode(array $attributes) { + $dom = Html::load('This placeholder should not be rendered.'); + $xpath = new \DOMXPath($dom); + $drupal_entity = $xpath->query('//drupal-entity')[0]; + foreach ($attributes as $attribute => $value) { + $drupal_entity->setAttribute($attribute, $value); + } + return Html::serialize($dom); + } + + /** + * Asserts that `@Filter=entity_embed` has been run successfully. + * + * @param string $output + * The string returned by the filtering process. + * + * @see \Drupal\Tests\entity_embed\Kernel\EntityEmbedFilterTestBase::createEmbedCode() + */ + protected function assertEntityEmbedFilterHasRun($output) { + $this->assertNotContains('This placeholder should not be rendered.', $output); + } + +} diff --git a/tests/src/Kernel/EntityEmbedFilterTranslationTest.php b/tests/src/Kernel/EntityEmbedFilterTranslationTest.php new file mode 100644 index 0000000..8bd6702 --- /dev/null +++ b/tests/src/Kernel/EntityEmbedFilterTranslationTest.php @@ -0,0 +1,164 @@ +save(); + // Reload the node to ensure it is aware of the newly created language. + $this->node = $this->container->get('entity.manager') + ->getStorage('node') + ->load($this->node->id()); + } + + /** + * Test translation title. + */ + public function testFilterTranslationTitle() { + $content = $this->createEmbedCode([ + 'data-entity-type' => 'node', + 'data-entity-uuid' => 'e7a3e1fe-b69b-417e-8ee4-c80cb7640e63', + 'data-view-mode' => 'teaser', + 'data-entity-embed-display' => 'entity_reference:entity_reference_label', + 'data-entity-embed-settings' => '{"link":"0"}', + ]); + + // Assert that title of embedded entity is unaffected when embedded entity + // lacks a matching translation. + $expected_title = $this->node->getTitle(); + $filter_result = $this->filter->process($content, 'en'); + $output = $filter_result->getProcessedText(); + $this->assertContains($expected_title, $output); + $filter_result = $this->filter->process($content, 'pt-br'); + $output = $filter_result->getProcessedText(); + $this->assertContains($expected_title, $output); + + // Translate the embedded entity to the same language as the context. + $this->node->addTranslation('pt-br') + ->getTranslation('pt-br') + ->setTitle('Embed em portugues') + ->save(); + + // Assert that title is translated when embedded entity has a matching + // translation. + $filter_result = $this->filter->process($content, 'pt-br'); + $output = $filter_result->getProcessedText(); + $this->assertContains($this->node->getTranslation('pt-br')->getTitle(), $output); + } + + /** + * Tests the filter in different translation contexts. + * + * @dataProvider providerTestFilterTranslations + */ + public function testFilterTranslations(array $embed_attributes, $process_lang, $assert_title_from_source, $assert_title_lang, $assert_extra) { + $content = $this->createEmbedCode($embed_attributes); + + // Translate the embedded entity to the same language as the context. + $this->node->addTranslation('pt-br') + ->getTranslation('pt-br') + ->setTitle('Embed em portugues') + ->save(); + + $filter_result = $this->filter->process($content, $process_lang); + $output = $filter_result->getProcessedText(); + + // Run the assertions based on the data. + if ($assert_title_from_source) { + $this->assertContains($this->node->getTitle(), $output); + // @TODO: find better naming and implementation for this, maybe? + if ($assert_extra) { + $this->assertNotContains($this->node->getTranslation($assert_extra)->getTitle(), $output); + } + } + else { + $this->assertContains($this->node->getTranslation($assert_title_lang)->getTitle(), $output); + } + } + + /** + * Data provider for testFilterTranslations. + */ + public function providerTestFilterTranslations() { + return [ + 'Change the translated context to explicitly embed the untranslated entity' => [ + 'embed_attributes' => [ + 'data-entity-type' => 'node', + 'data-entity-uuid' => 'e7a3e1fe-b69b-417e-8ee4-c80cb7640e63', + 'data-view-mode' => 'teaser', + 'data-entity-embed-display' => 'entity_reference:entity_reference_label', + 'data-entity-embed-settings' => '{"link":"0"}', + 'data-langcode' => 'en', + ], + 'process_lang' => 'pt-br', + 'assert_title_from_source' => TRUE, + 'assert_title_lang' => NULL, + 'assert_extra' => 'pt-br', + ], + 'Change the untranslated context to explicitly embed the Portugues translation of the embedded entity.' => [ + 'embed_attributes' => [ + 'data-entity-type' => 'node', + 'data-entity-uuid' => 'e7a3e1fe-b69b-417e-8ee4-c80cb7640e63', + 'data-view-mode' => 'teaser', + 'data-entity-embed-display' => 'entity_reference:entity_reference_label', + 'data-entity-embed-settings' => '{"link":"0"}', + 'data-langcode' => 'pt-br', + ], + 'process_lang' => 'pt-br', + 'assert_title_from_source' => FALSE, + 'assert_title_lang' => 'pt-br', + 'assert_extra' => NULL, + ], + 'Change the translated context to explicitly embed a non-existing translation, en' => [ + 'embed_attributes' => [ + 'data-entity-type' => 'node', + 'data-entity-uuid' => 'e7a3e1fe-b69b-417e-8ee4-c80cb7640e63', + 'data-view-mode' => 'teaser', + 'data-entity-embed-display' => 'entity_reference:entity_reference_label', + 'data-entity-embed-settings' => '{"link":"0"}', + 'data-langcode' => 'nl', + ], + 'process_lang' => 'en', + 'assert_title_from_source' => TRUE, + 'assert_title_lang' => NULL, + 'assert_extra' => NULL, + ], + 'Change the translated context to explicitly embed a non-existing translation, pt-br' => [ + 'embed_attributes' => [ + 'data-entity-type' => 'node', + 'data-entity-uuid' => 'e7a3e1fe-b69b-417e-8ee4-c80cb7640e63', + 'data-view-mode' => 'teaser', + 'data-entity-embed-display' => 'entity_reference:entity_reference_label', + 'data-entity-embed-settings' => '{"link":"0"}', + 'data-langcode' => 'nl', + ], + 'process_lang' => 'pt-br', + 'assert_title_from_source' => TRUE, + 'assert_title_lang' => NULL, + 'assert_extra' => NULL, + ], + ]; + } + +}