 .../src/Kernel/EntityEmbedFilterOverridesTest.php  | 154 ++++++
 tests/src/Kernel/EntityEmbedFilterTest.php         | 527 ++++++++++++++++++++
 .../EntityEmbedFilterTestBase.php}                 | 539 +++++----------------
 .../Kernel/EntityEmbedFilterTranslationTest.php    | 118 +++++
 4 files changed, 929 insertions(+), 409 deletions(-)

diff --git a/tests/src/Kernel/EntityEmbedFilterOverridesTest.php b/tests/src/Kernel/EntityEmbedFilterOverridesTest.php
new file mode 100644
index 0000000..0b84cbe
--- /dev/null
+++ b/tests/src/Kernel/EntityEmbedFilterOverridesTest.php
@@ -0,0 +1,154 @@
+<?php
+
+namespace Drupal\Tests\entity_embed\Kernel;
+
+use Drupal\field\Entity\FieldConfig;
+use Drupal\file\Entity\File;
+use Drupal\media\Entity\Media;
+use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
+use Drupal\Tests\TestFileCreationTrait;
+
+/**
+ * Tests that entity embeds can have per-embed overrides for e.g. `alt`.
+ *
+ * @group entity_embed
+ */
+class EntityEmbedFilterOverridesTest extends EntityEmbedFilterTestBase {
+
+  use MediaTypeCreationTrait;
+  use TestFileCreationTrait;
+
+  /**
+   * The image file to use in tests.
+   *
+   * @var \Drupal\file\FileInterface
+   */
+  protected $image;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'file',
+    'image',
+    'media',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->installSchema('file', ['file_usage']);
+    $this->installEntitySchema('file');
+    $this->installEntitySchema('media');
+    $this->installConfig('image');
+    $this->installConfig('media');
+    $this->installConfig('system');
+
+    $this->image = File::create([
+      'uri' => $this->getTestFiles('image')[0]->uri,
+      'uid' => 2,
+    ]);
+    $this->image->setPermanent();
+    $this->image->save();
+  }
+
+  /**
+   * Tests overriding of `alt` and `title` for default image field formatter.
+   */
+  public function testOverrideAltAndTitleForImage() {
+    $content = $this->createEmbedCode([
+      'data-entity-type' => 'file',
+      'data-entity-uuid' => $this->image->uuid(),
+      'data-entity-embed-display' => 'image:image',
+      'data-entity-embed-display-settings' => '{"image_style":"","image_link":""}',
+      'alt' => 'This is alt text',
+      'title' => 'This is title text',
+    ]);
+
+    $this->applyFilter($content);
+
+    $this->assertHasAttributes($this->cssSelect('div.embedded-entity')[0], [
+      'alt' => 'This is alt text',
+      'data-entity-embed-display' => 'image:image',
+      'data-entity-type' => 'file',
+      'data-entity-uuid' => $this->image->uuid(),
+      'title' => 'This is title text',
+      'data-langcode' => 'en',
+    ]);
+    $this->assertHasAttributes($this->cssSelect('div.embedded-entity img')[0], [
+      'alt' => 'This is alt text',
+      'title' => 'This is title text',
+    ]);
+  }
+
+  /**
+   * Tests overriding of `alt` and `title` for image media items.
+   */
+  public function testOverridesAltAndTitleForImageMedia() {
+    $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']));
+
+    $media = Media::create([
+      'bundle' => 'image',
+      'name' => 'Screaming hairy armadillo',
+      'field_media_image' => [
+        [
+          'target_id' => $this->image->id(),
+          'alt' => 'default alt',
+          'title' => 'default title',
+        ],
+      ],
+    ]);
+    $media->save();
+
+    $base = [
+      'data-entity-embed-display' => 'view_mode:media.full',
+      'data-entity-embed-display-settings' => '',
+      'data-entity-type' => 'media',
+      'data-entity-uuid' => $media->uuid(),
+    ];
+    $input = $this->createEmbedCode($base);
+    $input .= $this->createEmbedCode([
+      'alt' => 'alt 1',
+      'title' => 'title 1',
+    ] + $base);
+    $input .= $this->createEmbedCode([
+      'alt' => 'alt 2',
+      'title' => 'title 2',
+    ] + $base);
+    $input .= $this->createEmbedCode([
+      'alt' => 'alt 3',
+      'title' => 'title 3',
+    ] + $base);
+
+    $this->applyFilter($input);
+
+    $img_nodes = $this->cssSelect('img');
+    $this->assertCount(4, $img_nodes);
+    $this->assertHasAttributes($img_nodes[0], [
+      'alt' => 'default alt',
+    ]);
+    $this->assertHasAttributes($img_nodes[1], [
+      'alt' => 'alt 1',
+      'title' => 'title 1',
+    ]);
+    $this->assertHasAttributes($img_nodes[2], [
+      'alt' => 'alt 2',
+      'title' => 'title 2',
+    ]);
+    $this->assertHasAttributes($img_nodes[3], [
+      'alt' => 'alt 3',
+      'title' => 'title 3',
+    ]);
+  }
+
+}
diff --git a/tests/src/Kernel/EntityEmbedFilterTest.php b/tests/src/Kernel/EntityEmbedFilterTest.php
new file mode 100644
index 0000000..1beae9c
--- /dev/null
+++ b/tests/src/Kernel/EntityEmbedFilterTest.php
@@ -0,0 +1,527 @@
+<?php
+
+namespace Drupal\Tests\entity_embed\Kernel;
+
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Cache\CacheableMetadata;
+use Drupal\Core\Entity\TranslatableInterface;
+use Drupal\Core\Render\RenderContext;
+
+/**
+ * Tests the entity_embed filter.
+ *
+ * @group entity_embed
+ */
+class EntityEmbedFilterTest extends EntityEmbedFilterTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->installConfig('system');
+  }
+
+  /**
+   * Ensures entities are rendered with correct data attributes.
+   *
+   * @dataProvider providerTestFilter
+   */
+  public function testFilter(array $embed_attributes, array $contains, array $not_contains, array $expected_attributes, $is_published, $allowed_to_view_unpublished, CacheableMetadata $expected_cacheability, array $expected_asset_libraries) {
+    $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));
+
+    $result = $this->applyFilter($content);
+
+    if (!$is_published && !$allowed_to_view_unpublished) {
+      $this->assertEmpty($this->getRawContent());
+      return;
+    }
+
+    $this->assertContainsMultiple($contains);
+    $this->assertNotContainsMultiple($not_contains);
+
+    $embedded_entity_container = $this->cssSelect('div.embedded-entity')[0];
+    $this->assertHasAttributes($embedded_entity_container, $expected_attributes);
+
+    // Expected bubbleable metadata.
+    $this->assertSame($result->getCacheTags(), $expected_cacheability->getCacheTags());
+    $this->assertSame($result->getCacheContexts(), $expected_cacheability->getCacheContexts());
+    $this->assertSame($result->getCacheMaxAge(), $expected_cacheability->getCacheMaxAge());
+    $this->assertSame(['library'], array_keys($result->getAttachments()));
+    $this->assertSame($result->getAttachments()['library'], $expected_asset_libraries);
+  }
+
+  /**
+   * Data provider for testFilter.
+   */
+  public function providerTestFilter() {
+    $expected_cacheability = (new CacheableMetadata())
+      ->setCacheTags(['config:filter.format.plain_text', 'config:filter.settings', 'node:1', 'node_view', 'user:2', 'user_view'])
+      ->setCacheContexts(['timezone', 'user.permissions'])
+      ->setCacheMaxAge(Cache::PERMANENT);
+    $expected_asset_libraries = ['entity_embed/caption'];
+
+    return [
+      '`data-entity-uuid` + `data-view-mode` ⇒ rendered' => [
+        '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,
+        'expected_cacheability' => $expected_cacheability,
+        'expected_asset_libraries' => $expected_asset_libraries,
+      ],
+      'data-entity-uuid + data-view-mode; unpublished ⇒ NOT rendered' => [
+        'embed_attributes' => [
+          'data-entity-type' => 'node',
+          'data-entity-uuid' => 'e7a3e1fe-b69b-417e-8ee4-c80cb7640e63',
+          'data-view-mode' => 'teaser',
+        ],
+        'contains' => [],
+        'not_contains' => [],
+        'expected_attributes' => [],
+        'published' => FALSE,
+        'view_unpublished' => FALSE,
+        'expected_cacheability' => $expected_cacheability,
+        'expected_asset_libraries' => $expected_asset_libraries,
+      ],
+      'data-entity-uuid + data-view-mode; unpublished + `view own unpublished` ⇒ rendered' => [
+        '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' => FALSE,
+        'view_unpublished' => TRUE,
+        'expected_cacheability' => CacheableMetadata::createFromObject($expected_cacheability)
+          ->setCacheContexts(['timezone', 'user', 'user.permissions']),
+        'expected_asset_libraries' => $expected_asset_libraries,
+      ],
+      '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,
+        'expected_cacheability' => $expected_cacheability,
+        'expected_asset_libraries' => $expected_asset_libraries,
+      ],
+      '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,
+        'expected_cacheability' => $expected_cacheability,
+        'expected_asset_libraries' => $expected_asset_libraries,
+      ],
+      '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,
+        'expected_cacheability' => $expected_cacheability,
+        'expected_asset_libraries' => $expected_asset_libraries,
+      ],
+    ];
+  }
+
+  /**
+   * Tests for invalid Entity.
+   *
+   * @dataProvider providerTestInvalidEntity
+   */
+  public function testInvalidEntity(array $embed_attributes, array $contains, array $not_contains) {
+    $content = $this->createEmbedCode($embed_attributes);
+
+    $this->applyFilter($content);
+
+    $this->assertContainsMultiple($contains);
+    $this->assertNotContainsMultiple($not_contains);
+  }
+
+  /**
+   * 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-uuid' => 'InvalidID',
+          'data-view-mode' => 'teaser',
+        ],
+        'contains' => [],
+        'not_contains' => [],
+      ],
+    ];
+  }
+
+  /**
+   * Test HTML tag with same attributes as drupal-entity not affected.
+   */
+  public function testContainerTagElementReplace() {
+    $content = $this->createEmbedCode([
+      'data-entity-type' => 'node',
+      'data-entity-uuid' => $this->node->uuid(),
+      'data-view-mode' => 'teaser',
+    ]);
+
+    $random_tag = 'random-tag-name';
+    $content = str_replace('drupal-entity', $random_tag, $content);
+
+    $filter_result = $this->processText($content, 'en', ['entity_embed']);
+    $output = $filter_result->getProcessedText();
+    $this->setRawContent($output);
+    // The text content of the <random-tag-name> tag should still exist since
+    // the filter should not have touched it at all. For the same reason, the
+    // content of the to-be-embedded entity should not be present.
+    $this->assertRaw('This placeholder should not be rendered.', $output);
+    $this->assertNoRaw($this->node->body->value, $output);
+
+    $untouched = $this->cssSelect($random_tag)[0];
+    $this->assertHasAttributes($untouched, [
+      'data-entity-type' => 'node',
+      'data-entity-uuid' => $this->node->uuid(),
+      'data-view-mode' => 'teaser',
+    ]);
+  }
+
+  /**
+   * @covers \Drupal\filter\Plugin\Filter\FilterAlign
+   * @covers \Drupal\filter\Plugin\Filter\FilterCaption
+   * @dataProvider providerFilterIntegration
+   */
+  public function testFilterIntegration(array $filter_ids, array $additional_attributes, $verification_selector, $expected_verification_success, array $expected_asset_libraries) {
+    $content = $this->createEmbedCode([
+      'data-entity-type' => 'node',
+      'data-entity-uuid' => 'e7a3e1fe-b69b-417e-8ee4-c80cb7640e63',
+      'data-view-mode' => 'teaser',
+    ] + $additional_attributes);
+    $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',
+    ];
+
+    $result = $this->processText($content, 'en', $filter_ids);
+    $this->setRawContent($result->getProcessedText());
+    $this->assertCount($expected_verification_success ? 1 : 0, $this->cssSelect($verification_selector));
+    $this->assertHasAttributes($this->cssSelect('div.embedded-entity')[0], $expected_attributes);
+
+    // Expected bubbleable metadata.
+    $expected_cacheability = (new CacheableMetadata())
+      ->setCacheTags(['config:filter.format.plain_text', 'config:filter.settings', 'node:1', 'node_view', 'user:2', 'user_view'])
+      ->setCacheContexts(['timezone', 'user.permissions'])
+      ->setCacheMaxAge(Cache::PERMANENT);
+    $this->assertSame($result->getCacheTags(), $expected_cacheability->getCacheTags());
+    $this->assertSame($result->getCacheContexts(), $expected_cacheability->getCacheContexts());
+    $this->assertSame($result->getCacheMaxAge(), $expected_cacheability->getCacheMaxAge());
+    $this->assertSame(['library'], array_keys($result->getAttachments()));
+    $this->assertSame($result->getAttachments()['library'], $expected_asset_libraries);
+  }
+
+  public function providerFilterIntegration() {
+    $default_asset_libraries = ['entity_embed/caption'];
+
+    $caption_additional_attributes = ['data-caption' => 'Yo.'];
+    $caption_verification_selector = 'figure figcaption';
+    $caption_test_cases = [
+      '`data-caption`; only `entity_embed` ⇒ caption absent' => [
+        ['entity_embed'],
+        $caption_additional_attributes,
+        $caption_verification_selector,
+        FALSE,
+        $default_asset_libraries,
+      ],
+      '`data-caption`; `filter_caption` + `entity_embed` ⇒ caption present' => [
+        ['filter_caption', 'entity_embed'],
+        $caption_additional_attributes,
+        $caption_verification_selector,
+        TRUE,
+        ['filter/caption', 'entity_embed/caption'],
+      ],
+    ];
+
+    $align_additional_attributes = ['data-align' => 'center'];
+    $align_verification_selector = 'drupal-entity.align-center';
+    $align_test_cases = [
+      '`data-align`; `entity_embed` ⇒ alignment absent' => [
+        ['entity_embed'],
+        $align_additional_attributes,
+        $align_verification_selector,
+        FALSE,
+        $default_asset_libraries,
+      ],
+      '`data-align`; `filter_align` + `entity_embed` ⇒ alignment present' => [
+        ['filter_align', 'entity_embed'],
+        $align_additional_attributes,
+        $align_verification_selector,
+        FALSE,
+        $default_asset_libraries,
+      ],
+    ];
+
+    $caption_and_align_test_cases = [
+      '`data-caption` + `data-align`; `filter_align` + `filter_caption` + `entity_embed` ⇒ aligned caption present' => [
+        ['filter_align', 'filter_caption', 'entity_embed'],
+        $align_additional_attributes + $caption_additional_attributes,
+        'figure.align-center figcaption',
+        TRUE,
+        ['filter/caption', 'entity_embed/caption'],
+      ],
+    ];
+
+    return $caption_test_cases + $align_test_cases + $caption_and_align_test_cases;
+  }
+
+  /**
+   * 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',
+    ]);
+    $this->applyFilter($content);
+    $this->assertRaw($this->node->body->value);
+  }
+
+  /**
+   * Tests BC for `data-entity-uuid`'s predecessor, `data-entity-id`.
+   *
+   * @group legacy
+   */
+  public function testEntityIdBackwardsCompatibility() {
+    $content = $this->createEmbedCode([
+      'data-entity-type' => 'node',
+      'data-entity-id' => 1,
+      'data-view-mode' => 'teaser',
+    ]);
+    $this->applyFilter($content);
+    $this->assertHasAttributes($this->cssSelect('div.embedded-entity')[0], [
+      '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',
+    ]);
+  }
+
+  /**
+   * Verifies `data-entity-id` is ignored when `data-entity-uuid` is present.
+   *
+   * @group legacy
+   */
+  public function testEntityIdIgnoredIfEntityUuidPresent() {
+    $nonsensical_id = $this->randomMachineName();
+    $content = $this->createEmbedCode([
+      'data-entity-type' => 'node',
+      'data-entity-uuid' => 'e7a3e1fe-b69b-417e-8ee4-c80cb7640e63',
+      'data-entity-id' => $nonsensical_id,
+      'data-view-mode' => 'teaser',
+    ]);
+    $this->applyFilter($content);
+    $this->assertHasAttributes($this->cssSelect('div.embedded-entity')[0], [
+      'data-entity-type' => 'node',
+      'data-entity-id' => $nonsensical_id,
+      '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',
+    ]);
+  }
+
+  /**
+   * Tests BC for`data-entity-embed-display-settings`'s predecessor.
+   *
+   * @group legacy
+   */
+  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"}',
+    ]);
+    $this->applyFilter($content);
+    $this->assertCount(0, $this->cssSelect('div.embedded-entity a'));
+    $this->assertSame($this->node->label(), (string) $this->cssSelect('div.embedded-entity')[0]);
+  }
+
+  /**
+   * Tests the placeholder for missing entities.
+   */
+  public function testMissingEntityPlaceholder() {
+    $content = $this->createEmbedCode([
+      'data-entity-type' => 'node',
+      'data-entity-uuid' => $this->node->uuid(),
+      'data-view-mode' => 'default',
+    ]);
+
+    $this->applyFilter($content);
+    $this->assertRaw($this->node->body->value);
+    $this->assertCount(1, $this->cssSelect('div.embedded-entity'));
+
+    $this->node->delete();
+
+    $this->applyFilter($content);
+    $this->assertNoRaw($this->node->body->value);
+    $this->assertCount(0, $this->cssSelect('div.embedded-entity'));
+    $deleted_embed_warning = $this->cssSelect('img')[0];
+    $this->assertNotEmpty($deleted_embed_warning);
+    $this->assertHasAttributes($deleted_embed_warning, [
+      'alt' => 'Deleted content encountered, site owner alerted.',
+      'src' => file_create_url('core/modules/media/images/icons/no-thumbnail.png'),
+      'title' => 'Deleted content.',
+    ]);
+  }
+
+  /**
+   * Loads an entity (in the appropriate translation) given HTML attributes.
+   *
+   * @param string[] $attributes
+   *   An array of HTML attributes, including at least `data-entity-type` and
+   *   `data-entity-uuid`, and optionally `data-langcode`.
+   *
+   * @return \Drupal\Core\Entity\EntityInterface|null
+   *   The requested entity, or NULL.
+   */
+  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);
+    }
+    if ($entity && $entity instanceof TranslatableInterface && !empty($attributes['data-langcode'])) {
+      if ($entity->hasTranslation($attributes['data-langcode'])) {
+        $entity = $entity->getTranslation($attributes['data-langcode']);
+      }
+    }
+
+    return $entity;
+  }
+
+  /**
+   * Asserts that a haystack contains multiple needles.
+   *
+   * @param array $needles
+   *   An array of strings to verify the output contains.
+   */
+  protected function assertContainsMultiple(array $needles) {
+    foreach ($needles as $needle) {
+      $this->assertRaw($needle);
+    }
+  }
+
+  /**
+   * Asserts that a haystack does not contain multiple needles.
+   *
+   * @param array $needles
+   *   An array of strings to verify the output does not contain.
+   */
+  protected function assertNotContainsMultiple(array $needles) {
+    foreach ($needles as $needle) {
+      $this->assertNoRaw($needle);
+    }
+  }
+
+}
diff --git a/tests/src/Functional/EntityEmbedFilterTest.php b/tests/src/Kernel/EntityEmbedFilterTestBase.php
similarity index 5%
rename from tests/src/Functional/EntityEmbedFilterTest.php
rename to tests/src/Kernel/EntityEmbedFilterTestBase.php
index 1c9be44..7395a13 100644
--- a/tests/src/Functional/EntityEmbedFilterTest.php
+++ b/tests/src/Kernel/EntityEmbedFilterTestBase.php
@@ -1,42 +1,53 @@
 <?php
 
-namespace Drupal\Tests\entity_embed\Functional;
+namespace Drupal\Tests\entity_embed\Kernel;
 
 use Drupal\Component\Utility\Html;
-use Drupal\language\Entity\ConfigurableLanguage;
-use Drupal\file\Entity\File;
-use Drupal\media\Entity\Media;
-use Drupal\node\Entity\Node;
+use Drupal\Core\Render\BubbleableMetadata;
+use Drupal\Core\Render\RenderContext;
 use Drupal\filter\FilterPluginCollection;
+use Drupal\filter\FilterProcessResult;
+use Drupal\KernelTests\KernelTestBase;
+use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
+use Drupal\Tests\node\Traits\NodeCreationTrait;
+use Drupal\Tests\user\Traits\UserCreationTrait;
 
 /**
- * Tests the entity_embed filter.
- *
- * @group entity_embed
+ * Base class for Entity Embed filter tests.
  */
-class EntityEmbedFilterTest extends EntityEmbedTestBase {
+abstract class EntityEmbedFilterTestBase extends KernelTestBase {
+
+  use NodeCreationTrait {
+    createNode as drupalCreateNode;
+  }
+  use UserCreationTrait {
+    createUser as drupalCreateUser;
+    createRole as drupalCreateRole;
+  }
+  use ContentTypeCreationTrait {
+    createContentType as drupalCreateContentType;
+  }
 
   /**
-   * Modules to enable.
-   *
-   * @var array
+   * {@inheritdoc}
    */
   protected static $modules = [
-    'content_translation',
-    'file',
-    'image',
+    'embed',
     'entity_embed',
-    'entity_embed_test',
+    'field',
+    'filter',
     'node',
-    'ckeditor',
+    'system',
+    'text',
+    'user',
   ];
 
   /**
-   * The entity embed filter.
+   * The sample Node entity to embed.
    *
-   * @var \Drupal\entity_embed\Plugin\Filter\EntityEmbedFilter
+   * @var \Drupal\node\NodeInterface
    */
-  protected $filter;
+  protected $node;
 
   /**
    * {@inheritdoc}
@@ -44,400 +55,42 @@ class EntityEmbedFilterTest extends EntityEmbedTestBase {
   protected function setUp() {
     parent::setUp();
 
-    $manager = $this->container->get('plugin.manager.filter');
-    $bag = new FilterPluginCollection($manager, []);
-    $this->filter = $bag->get('entity_embed');
-  }
-
-  /**
-   * Tests the entity_embed filter.
-   *
-   * Ensures that entities are getting rendered when correct data attributes
-   * are passed. Also tests situations when embed fails.
-   */
-  public function testFilter() {
-    $assert_session = $this->assertSession();
-
-    // Tests entity embed using entity ID and view mode.
-    $content = '<drupal-entity data-entity-type="node" data-entity-id="' . $this->node->id() . '" data-view-mode="teaser">This placeholder should not be rendered.</drupal-entity>';
-    $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('<drupal-entity data-entity-type="node" data-entity');
-    $this->assertSession()->responseContains($this->node->body->value);
-    $this->assertSession()->responseNotContains('This placeholder should not be rendered.');
-    $this->assertSession()->responseContains('<div data-entity-type="node" data-entity-id="1" data-view-mode="teaser" data-entity-uuid="' . $this->node->uuid() . '" data-langcode="en" data-entity-embed-display="entity_reference:entity_reference_entity_view" data-entity-embed-display-settings="teaser" class="embedded-entity">');
-
-    // 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('<drupal-entity data-entity-type="node" data-entity');
-    $this->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('<drupal-entity data-entity-type="node" data-entity');
-    $this->assertSession()->responseContains($this->node->body->value);
-    $this->assertSession()->responseNotContains('This placeholder should not be rendered.');
-    $this->assertSession()->responseContains('<div data-entity-type="node" data-entity-id="1" data-view-mode="teaser" data-entity-uuid="' . $this->node->uuid() . '" data-langcode="en" data-entity-embed-display="entity_reference:entity_reference_entity_view" data-entity-embed-display-settings="teaser" class="embedded-entity">');
-    $this->webUser->removeRole('access_unpublished');
-    $this->webUser->save();
-    $this->node->setPublished(TRUE)->save();
-
-    // Tests entity embed using entity UUID and view mode.
-    $content = '<drupal-entity data-entity-type="node" data-entity-uuid="' . $this->node->uuid() . '" data-view-mode="teaser">This placeholder should not be rendered.</drupal-entity>';
-    $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('<drupal-entity data-entity-type="node" data-entity');
-    $this->assertSession()->responseContains($this->node->body->value);
-    $this->assertSession()->responseNotContains('This placeholder should not be rendered.');
-    $this->assertSession()->responseContains('<div data-entity-type="node" data-entity-uuid="' . $this->node->uuid() . '" data-view-mode="teaser" data-langcode="en" data-entity-embed-display="entity_reference:entity_reference_entity_view" data-entity-embed-display-settings="teaser" class="embedded-entity">');
-    $this->assertSession()->responseHeaderContains('X-Drupal-Cache-Tags', 'foo:' . $this->node->id());
-
-    // Ensure that placeholder is not replaced when embed is unsuccessful.
-    $content = '<drupal-entity data-entity-type="node" data-entity-id="InvalidID" data-view-mode="teaser">This placeholder should be rendered since specified entity does not exists.</drupal-entity>';
-    $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('<drupal-entity data-entity-type="node" data-entity');
-    $this->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 = '<drupal-entity data-entity-type="node" data-entity-id="' . $sample_node->id() . '" data-entity-uuid="' . $this->node->uuid() . '" data-view-mode="teaser">This placeholder should not be rendered.</drupal-entity>';
-    $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('<drupal-entity data-entity-type="node" data-entity');
-    $this->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('<div data-entity-type="node" data-entity-id="' . $sample_node->id() . '" data-entity-uuid="' . $this->node->uuid() . '" data-view-mode="teaser" data-langcode="en" data-entity-embed-display="entity_reference:entity_reference_entity_view" data-entity-embed-display-settings="teaser" class="embedded-entity">');
-
-    // Test deprecated 'default' Entity Embed Display plugin.
-    $content = '<drupal-entity data-entity-type="node" data-entity-uuid="' . $this->node->uuid() . '" data-entity-embed-display="default" data-entity-embed-display-settings=\'{"view_mode":"teaser"}\'>This placeholder should not be rendered.</drupal-entity>';
-    $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('<div data-entity-type="node" data-entity-uuid="' . $this->node->uuid() . '" data-entity-embed-display="entity_reference:entity_reference_entity_view" data-entity-embed-display-settings="teaser" data-langcode="en" class="embedded-entity">');
-
-    // Ensure that Entity Embed Display plugin is preferred over view mode when
-    // both attributes are present.
-    $content = '<drupal-entity data-entity-type="node" data-entity-uuid="' . $this->node->uuid() . '" data-entity-embed-display="default" data-entity-embed-display-settings=\'{"view_mode":"full"}\' data-view-mode="some-invalid-view-mode" data-align="left" data-caption="test caption">This placeholder should not be rendered.</drupal-entity>';
-    $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
-    // <drupal-entity>.
-    $content = '<not-drupal-entity data-entity-type="node" data-entity-id="' . $this->node->id() . '" data-view-mode="teaser">this placeholder should not be rendered.</not-drupal-entity>';
-    $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('</not-drupal-entity>');
-    $content = '<div data-entity-type="node" data-entity-id="' . $this->node->id() . '" data-view-mode="teaser">this placeholder should not be rendered.</div>';
-    $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('<div data-entity-type="node" data-entity-id');
-
-    // Test that attributes are correctly added when image formatter is used.
-    /** @var \Drupal\file\FileInterface $image */
-    $image = $this->getTestFile('image');
-    $image->setPermanent();
-    $image->save();
-    $content = '<drupal-entity 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">This placeholder should not be rendered.</drupal-entity>';
-    $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 = '<drupal-entity data-entity-type="node" data-entity-uuid="' . $this->node->uuid() . '" data-entity-embed-display="entity_reference:entity_reference_label" data-entity-embed-settings=\'{"link":"0"}\' data-align="left" data-caption="test caption">This placeholder should not be rendered.</drupal-entity>';
-    $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 = '<drupal-entity data-foo="bar" foo="bar" data-entity-type="node" data-entity-uuid="' . $this->node->uuid() . '" data-view-mode="teaser">This placeholder should not be rendered.</drupal-entity>';
-    $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('<div data-foo="bar" foo="bar" data-entity-type="node" data-entity-uuid="' . $this->node->uuid() . '" data-view-mode="teaser" data-langcode="en" data-entity-embed-display="entity_reference:entity_reference_entity_view" data-entity-embed-display-settings="teaser" class="embedded-entity">');
-
-    // Tests the placeholder for missing entities.
-    $embedded_node = $this->drupalCreateNode([
-      'type' => 'page',
-      'title' => 'Embedded node',
-      'body' => [['value' => 'Embedded text content', 'format' => 'custom_format']],
+    $this->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',
     ]);
-    $content = '<drupal-entity data-entity-type="node" data-entity-uuid="' . $embedded_node->uuid() . '" data-view-mode="default"></drupal-entity>';
-    $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);
-  }
+    $this->container->set('current_user', $user);
 
-  /**
-   * Tests the filter in different translation contexts.
-   */
-  public function testTranslation() {
-    $content = '<drupal-entity data-entity-type="node" data-entity-uuid="' . $this->node->uuid() . '" data-entity-embed-display="entity_reference:entity_reference_label" data-entity-embed-settings=\'{"link":"0"}\' data-align="left" data-caption="test caption">This placeholder should not be rendered.</drupal-entity>';
-
-    ConfigurableLanguage::createFromLangcode('pt-br')->save();
-    $host_entity = $this->drupalCreateNode([
-      'type' => 'page',
+    // Create a sample node to be embedded.
+    $this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
+    $this->node = $this->drupalCreateNode([
+      'title' => 'Embed Test Node',
       'body' => [
-        'value' => $content,
-        'format' => 'custom_format',
-      ],
-    ]);
-    $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 = '<drupal-entity data-entity-type="node" data-entity-uuid="' . $this->node->uuid() . '" data-entity-embed-display="entity_reference:entity_reference_label" data-entity-embed-settings=\'{"link":"0"}\' data-align="left" data-caption="test caption" data-langcode="' . $host_entity->language()->getId() . '">This placeholder should not be rendered.</drupal-entity>';
-    $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 = '<drupal-entity data-entity-type="node" data-entity-uuid="' . $this->node->uuid() . '" data-entity-embed-display="entity_reference:entity_reference_label" data-entity-embed-settings=\'{"link":"0"}\' data-align="left" data-caption="test caption" data-langcode="pt-br">This placeholder should not be rendered.</drupal-entity>';
-    $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 = '<drupal-entity data-entity-type="node" data-entity-uuid="' . $this->node->uuid() . '" data-entity-embed-display="entity_reference:entity_reference_label" data-entity-embed-settings=\'{"link":"0"}\' data-align="left" data-caption="test caption" data-langcode="nl">This placeholder should not be rendered.</drupal-entity>';
-    $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 = '<drupal-entity data-entity-type="node" data-entity-uuid="' . $this->node->uuid() . '" data-entity-embed-display="entity_reference:entity_reference_label" data-entity-embed-settings=\'{"link":"0"}\' data-align="left" data-caption="test caption" data-langcode="nl">This placeholder should not be rendered.</drupal-entity>';
-    $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() {
-    \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(),
-    ]);
-    $file->save();
-
-    $this->createNode([
-      'type' => 'article',
-      'title' => 'Red-lipped batfish',
-    ]);
-
-    $media = Media::create([
-      'bundle' => 'image',
-      'name' => 'Screaming hairy armadillo',
-      'field_media_image' => [
-        [
-          'target_id' => $file->id(),
-          'alt' => 'default alt',
-          'title' => 'default title',
-        ],
+        'value' => 'This node is to be used for embedding in other nodes.',
       ],
-    ]);
-    $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-settings' => '',
-      'data-entity-type' => 'media',
-      'data-entity-uuid' => $media->uuid(),
-      'data-langcode' => 'en',
-    ]);
-    $input .= $this->createEmbedCode([
-      '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-settings' => '',
-      'data-entity-type' => 'media',
-      'data-entity-uuid' => $media->uuid(),
-      'data-langcode' => 'en',
-    ]);
-    $input .= $this->createEmbedCode([
-      '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-settings' => '',
-      'data-entity-type' => 'media',
-      'data-entity-uuid' => $media->uuid(),
-      'data-langcode' => 'en',
-    ]);
-
-    /** @var \Drupal\filter\FilterProcessResult $filter_result */
-    $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);
-    $dom = Html::load($output);
-    $xpath = new \DOMXPath($dom);
-
-    $img1 = $xpath->query("//img[contains(@alt, 'alt 1')]")[0];
-    $this->assertNotEmpty($img1);
-    $this->assertHasAttributes($img1, [
-      'alt' => 'alt 1',
-      'title' => 'title 1',
-    ]);
-
-    $img2 = $xpath->query("//img[contains(@alt, 'alt 2')]")[0];
-    $this->assertNotEmpty($img2);
-    $this->assertHasAttributes($img2, [
-      'alt' => 'alt 2',
-      'title' => 'title 2',
-    ]);
-
-    $img3 = $xpath->query("//img[contains(@alt, 'alt 3')]")[0];
-    $this->assertNotEmpty($img3);
-    $this->assertHasAttributes($img3, [
-      'alt' => 'alt 3',
-      'title' => 'title 3',
+      'uuid' => 'e7a3e1fe-b69b-417e-8ee4-c80cb7640e63',
     ]);
   }
 
   /**
-   * Creates an embed code with the given attributes.
+   * Gets an embed code with given attributes.
    *
    * @param array $attributes
-   *   The attributes to set.
+   *   The attributes to add.
    *
    * @return string
-   *   A HTML string containing a <drupal-entity> tag with the given attributes.
+   *   A string containing a drupal-entity dom element.
+   *
+   * @see assertEntityEmbedFilterHasRun()
    */
   protected function createEmbedCode(array $attributes) {
     $dom = Html::load('<drupal-entity>This placeholder should not be rendered.</drupal-entity>');
@@ -450,18 +103,86 @@ class EntityEmbedFilterTest extends EntityEmbedTestBase {
   }
 
   /**
-   * Asserts that the DOMNode object has the given attributes.
+   * Applies the `@Filter=entity_embed` filter to text, pipes to raw content.
+   *
+   * @param string $text
+   *   The text string to be filtered.
+   * @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.
+   *
+   * @see \Drupal\Tests\entity_embed\Kernel\EntityEmbedFilterTestBase::createEmbedCode()
+   * @see \Drupal\KernelTests\AssertContentTrait::setRawContent()
+   */
+  protected function applyFilter($text, $langcode = 'en') {
+    $this->assertContains('<drupal-entity', $text);
+    $this->assertContains('This placeholder should not be rendered.', $text);
+    $filter_result = $this->processText($text, $langcode);
+    $output = $filter_result->getProcessedText();
+    $this->assertNotContains('<drupal-entity', $output);
+    $this->assertNotContains('This placeholder should not be rendered.', $output);
+    $this->setRawContent($output);
+    return $filter_result;
+  }
+
+  /**
+   * Assert that the SimpleXMLElement object has the given attributes.
    *
-   * @param \DOMNode $node
-   *   The DOMNode object to check.
+   * @param \SimpleXMLElement $element
+   *   The SimpleXMLElement 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) {
+  protected function assertHasAttributes(\SimpleXMLElement $element, array $attributes) {
     foreach ($attributes as $attribute => $value) {
-      $this->assertEquals($value, $node->getAttribute($attribute));
+      $this->assertSame((string) $value, (string) $element[$attribute]);
+    }
+  }
+
+  /**
+   * Processes text through the provided filters.
+   *
+   * @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 = 'und', array $filter_ids = ['entity_embed']) {
+    $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;
   }
 
 }
diff --git a/tests/src/Kernel/EntityEmbedFilterTranslationTest.php b/tests/src/Kernel/EntityEmbedFilterTranslationTest.php
new file mode 100644
index 0000000..7562ddf
--- /dev/null
+++ b/tests/src/Kernel/EntityEmbedFilterTranslationTest.php
@@ -0,0 +1,118 @@
+<?php
+
+namespace Drupal\Tests\entity_embed\Kernel;
+
+use Drupal\language\Entity\ConfigurableLanguage;
+
+/**
+ * Tests the entity_embed filter with translated content.
+ *
+ * @group entity_embed
+ */
+class EntityEmbedFilterTranslationTest extends EntityEmbedFilterTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'content_translation',
+    'language',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    ConfigurableLanguage::createFromLangcode('pt-br')->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();
+    $this->applyFilter($content, 'en');
+    $this->assertRaw($expected_title);
+    $this->applyFilter($content, 'pt-br');
+    $this->assertRaw($expected_title);
+
+    // Translate the embedded entity to the same language as the context (i.e.
+    // the language of the host entity).
+    $this->node->addTranslation('pt-br')
+      ->setTitle('Embed em portugues')
+      ->save();
+
+    // Assert that title is translated when embedded entity has a matching
+    // translation.
+    $this->applyFilter($content, 'pt-br');
+    $this->assertRaw($this->node->getTranslation('pt-br')->getTitle());
+  }
+
+  /**
+   * Tests the filter in different translation contexts.
+   *
+   * @dataProvider providerTestFilterTranslations
+   */
+  public function testFilterTranslations($data_langcode_attribute, $expected_title_langcode) {
+    $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"}',
+      'data-langcode' => $data_langcode_attribute,
+    ]);
+
+    // Translate the embedded entity to the same language as the context (i.e.
+    // the language of the host entity).
+    $this->node->addTranslation('pt-br')
+      ->setTitle('Embed em portugues')
+      ->save();
+
+    // The embedded entity's language is explicitly specified and therefore does
+    // not depend on the context. Prove by specifying a non-existent language.
+    $this->applyFilter($content, $this->randomMachineName());
+    $this->assertRaw($this->node->getTranslation($expected_title_langcode)->getTitle());
+  }
+
+  /**
+   * Data provider for testFilterTranslations.
+   */
+  public function providerTestFilterTranslations() {
+    return [
+      'Change the translated context to explicitly embed the untranslated entity' => [
+        'data-langcode' => 'en',
+        'en',
+      ],
+      'Change the untranslated context to explicitly embed the Portugues translation of the embedded entity.' => [
+        'data-langcode' => 'pt-br',
+        'pt-br',
+      ],
+      'Change the translated context to explicitly embed a non-existing translation, en' => [
+        'data-langcode' => 'nl',
+        'en',
+      ],
+      'Change the translated context to explicitly embed a non-existing translation, pt-br' => [
+        'data-langcode' => 'nl',
+        'en',
+      ],
+    ];
+  }
+
+}
