Problem/Motivation

Quoting Wim Leers from #2940029-27: Add an input filter to display embedded Media entities:

  • test coverage is super minimal for now, it's painful to write test coverage for now because Entity Embed's test coverage is doing rather raw HTML assertions, which are impossible to transform to Media entity HTML assertions because Media entities are rendered in more complex ways; we should first improve \Drupal\Tests\entity_embed\Functional\EntityEmbedFilterTest further

Proposed resolution

  1. Some of those "raw HTML assertions" have been fixed in #2752253: Document correct filter_html_image_secure + filter_align + filter_caption + entity_embed filters order, write tests, and ensure correct by default, so you can find a few examples in there. But it's not yet testing all possible permutations of:
    • embedded entity revisions (Entity Embed does not support referencing a particular revision)
    • ✅ embedded entity translation vs host entity translation (and translation exists vs fallback vs missing)
    • ✅ embedded entity accessible vs inaccessible
    • data-* attributes choosing different behavior

    That's what we'll need to bring a subset of this @Filter plugin to Drupal core in #2940029: Add an input filter to display embedded Media entities. Rather than writing those tests only for #2940029, it'd be great to write it for Entity Embed first, for the feature set that we'll be bringing to Drupal core. (Meaning we don't need as extensive test coverage for entity embed display plugin settings, since we won't bring that to Drupal core.)

  2. Also missing:
  3. ✅ The current \Drupal\Tests\entity_embed\Functional\EntityEmbedFilterTest is a functional test. Ideally it'd become a Unit test, or at least a Kernel test. For an example Unit test for a @Filter plugin, see \Drupal\Tests\filter\Unit\FilterHtmlTest, for Kernel see \Drupal\Tests\filter\Kernel\FilterCaptionTwigDebugTest.

    A Unit test is achievable when testing only \Drupal\entity_embed\Plugin\Filter\EntityEmbedFilter (although I'm not sure it's possible in this case, since the filter needs to load the to-be-embedded entity), a Kernel test is achievable when testing a combination of multiple filters (the entity embed filter in combination with the alignment and caption filters).

Remaining tasks

  • Remove drupalGet() calls in preparation for conversion to Kernel test
  • Convert to Kernel Test
  • Add additional edge cases

User interface changes

None.

API changes

None.

Data model changes

None.

CommentFileSizeAuthor
#135 Screenshot 2019-06-22 at 01.11.30.png1.92 MBWim Leers
#132 3052482-132.patch60.44 KBWim Leers
#132 interdiff.txt8.22 KBWim Leers
#131 3052482-131.patch60.81 KBWim Leers
#131 interdiff.txt3.32 KBWim Leers
#128 3052482-128.patch60.76 KBWim Leers
#128 interdiff.txt2.93 KBWim Leers
#127 3052482-127.patch60.89 KBWim Leers
#127 interdiff.txt662 bytesWim Leers
#126 3052482-126.patch60.95 KBWim Leers
#126 interdiff.txt8.98 KBWim Leers
#125 3052482-125.patch61.57 KBWim Leers
#125 interdiff.txt4.53 KBWim Leers
#124 3052482-124.patch61.66 KBWim Leers
#124 interdiff.txt6.41 KBWim Leers
#123 3052482-123.patch58.5 KBWim Leers
#123 interdiff.txt9.48 KBWim Leers
#122 3052482-122.patch59.16 KBWim Leers
#122 interdiff.txt4.8 KBWim Leers
#121 3052482-121.patch59.84 KBWim Leers
#121 interdiff.txt4.22 KBWim Leers
#120 3052482-120.patch60.49 KBWim Leers
#120 interdiff.txt7.52 KBWim Leers
#119 3052482-119.patch59.91 KBWim Leers
#119 interdiff.txt5.41 KBWim Leers
#118 3052482-118.patch60.72 KBWim Leers
#118 interdiff.txt15.86 KBWim Leers
#117 3052482-117.patch60.62 KBWim Leers
#117 interdiff.txt5 KBWim Leers
#116 3052482-116.patch61.42 KBWim Leers
#116 interdiff.txt1.87 KBWim Leers
#115 3052482-115.patch61.25 KBWim Leers
#115 interdiff.txt2.98 KBWim Leers
#114 3052482-114.patch60.64 KBWim Leers
#114 interdiff.txt5.16 KBWim Leers
#113 3052482-113.patch61.35 KBWim Leers
#113 interdiff.txt3.9 KBWim Leers
#112 3052482-112.patch60.89 KBWim Leers
#112 interdiff.txt3.57 KBWim Leers
#107 3052482-107.patch61.93 KBWim Leers
#107 interdiff.txt9.35 KBWim Leers
#105 3052482-105.patch62.59 KBWim Leers
#105 interdiff.txt5.11 KBWim Leers
#104 3052482-104.patch61.05 KBWim Leers
#104 interdiff.txt3.08 KBWim Leers
#103 3052482-103.patch61.11 KBWim Leers
#103 interdiff.txt3.17 KBWim Leers
#102 3052482-102.patch61.05 KBWim Leers
#102 interdiff.txt711 bytesWim Leers
#101 3052482-101.patch61.04 KBWim Leers
#101 interdiff.txt4.87 KBWim Leers
#100 interdiff.txt3.82 KBWim Leers
#100 3052482-100.patch61.19 KBWim Leers
#99 3052482-98.patch59.95 KBWim Leers
#99 interdiff.txt9.46 KBWim Leers
#95 3052482-95.patch56.12 KBWim Leers
#95 interdiff.txt4.36 KBWim Leers
#93 3052482-93.patch56.84 KBWim Leers
#93 interdiff.txt5.27 KBWim Leers
#92 3052482-92.patch57.14 KBWim Leers
#92 interdiff.txt4.06 KBWim Leers
#90 interdiff.txt2.81 KBWim Leers
#90 3052482-90.patch57.34 KBWim Leers
#89 3052482-89.patch57.21 KBWim Leers
#89 interdiff.txt6.98 KBWim Leers
#87 3052482-87.patch57.7 KBWim Leers
#87 interdiff.txt7.62 KBWim Leers
#86 3052482-86.patch58.42 KBWim Leers
#86 interdiff.txt2 KBWim Leers
#82 3052482-82.patch58.59 KBWim Leers
#82 interdiff.txt3.06 KBWim Leers
#81 3052482-81.patch58.66 KBWim Leers
#81 interdiff.txt2.14 KBWim Leers
#80 3052482-80.patch58.72 KBWim Leers
#80 interdiff.txt3.41 KBWim Leers
#79 3052482-79.patch58.66 KBWim Leers
#79 interdiff.txt3.39 KBWim Leers
#78 3052482-78.patch57.49 KBWim Leers
#78 interdiff.txt2.23 KBWim Leers
#77 3052482-77.patch59.66 KBWim Leers
#77 interdiff.txt16.13 KBWim Leers
#76 3052482-76.patch58.56 KBWim Leers
#76 interdiff.txt1.14 KBWim Leers
#75 3052482-75.patch58.72 KBWim Leers
#75 interdiff.txt3.42 KBWim Leers
#74 3052482-73.patch57.13 KBWim Leers
#74 interdiff.txt1.92 KBWim Leers
#72 3052482-72.patch57.32 KBWim Leers
#72 interdiff.txt838 bytesWim Leers
#67 3052482-67.patch57.31 KBWim Leers
#67 interdiff.txt5.4 KBWim Leers
#66 3052482-66.patch57.11 KBWim Leers
#66 interdiff.txt2.67 KBWim Leers
#65 3052482-65.patch57.86 KBWim Leers
#65 interdiff.txt2.48 KBWim Leers
#63 3052482-63.patch58.87 KBWim Leers
#63 interdiff.txt2.52 KBWim Leers
#62 3052482-62.patch59.17 KBWim Leers
#62 interdiff-ported-54.txt2.55 KBWim Leers
#62 interdiff-ported-51_and_53.txt3.49 KBWim Leers
#61 3052482-61.patch58.73 KBWim Leers
#61 interdiff.txt13.33 KBWim Leers
#60 13151825-60.patch64.64 KBoknate
#60 13151825--interdiff-56-60.txt1.42 KBoknate
#56 13151825-56.patch64.17 KBoknate
#56 13151825--interdiff-54-56.txt19.65 KBoknate
#54 13151825--interdiff-53-54.txt2.39 KBoknate
#54 13151825-54.patch66.68 KBoknate
#53 13151825-53.patch66.23 KBoknate
#53 13151825--interdiff-49-53.txt1.65 KBoknate
#51 13151825-49.patch66.26 KBoknate
#51 13151825-interdiff--48-49.txt2.94 KBoknate
#49 3052482-48.patch59.77 KBWim Leers
#49 interdiff.txt1.38 KBWim Leers
#47 3052482-47.patch59.61 KBWim Leers
#47 interdiff.txt10.36 KBWim Leers
#43 3052482-43.patch53.38 KBWim Leers
#43 interdiff.txt2.48 KBWim Leers
#40 3052482-40.patch53.55 KBwannesdr
#40 3052482-40-interdiff.txt4.83 KBwannesdr
#38 3052482-38-interdiff.txt1.73 KBwannesdr
#38 3052482-38.patch53.77 KBwannesdr
#37 3052482-37.patch54.14 KBwannesdr
#37 3052482-37-interdiff.txt1.54 KBwannesdr
#34 3052482-34.patch54.13 KBwannesdr
#34 3052482-34-interdiff.txt1.11 KBwannesdr
#32 3052482-32.patch54.1 KBwannesdr
#32 3052482-32-interdiff.txt8.54 KBwannesdr
#31 3052482-31-interdiff.txt539 byteswannesdr
#31 3052482-31.patch54.4 KBwannesdr
#29 3052482-28-interdiff.txt1.06 KBwannesdr
#29 3052482-28.patch54.32 KBwannesdr
#27 3052482-27.patch54.31 KBwannesdr
#27 3052482-27-interdiff.txt33.76 KBwannesdr
#25 3052482-25-interdiff.patch7.65 KBwannesdr
#25 3052482-25.patch48.78 KBwannesdr
#24 3052482-22.patch49.51 KBwannesdr
#21 3052482-21.patch15.76 KBwannesdr
#21 3052482-21-interdiff.txt15.78 KBwannesdr
#18 entity-embed-3059394-18.patch46.17 KBoknate
#12 3052482--interdiff-11-12.txt1016 bytesoknate
#12 entity-embed-filter-test-revamp-3052482-12.patch43.9 KBoknate
#11 3052482--interdiff-9-11.txt10.21 KBoknate
#11 entity-embed-filter-test-revamp-3052482-11.patch43.89 KBoknate
#10 entity-embed-filter-test-revamp-3052482-10.patch35.23 KBoknate
#9 entity-embed-filter-test-revamp-3052482-9.patch34.3 KBoknate
#9 3052482--interdiff-7-9.txt5.07 KBoknate
#7 3052482--interdiff-6-7.txt18.42 KBoknate
#7 entity-embed-filter-test-revamp-3052482-7.patch31.43 KBoknate
#6 entity-embed-filter-test-revamp-3052482-6.patch14.3 KBoknate
#4 entity-embed-filter-test-revamp-3052482-4.patch7.75 KBoknate
#3 3052482-3.patch5.32 KBmarcoscano
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Wim Leers created an issue. See original summary.

Wim Leers’s picture

Title: Expand EntityEmbedFilter's test coverage » Expand EntityEmbedFilter's test coverage: test many more edge cases, hopefully change to kernel test
Issue summary: View changes
marcoscano’s picture

Status: Active » Needs work
FileSize
5.32 KB

Started working on this, but ran out of time.

Tried to create a unit test but it was quite hard with so many services needed to render the entity inside the filter. The Kernel test seems more doable though, here is the baseline of what I was thinking for it. Will continue with the other test cases as soon as I find more time :) .

oknate’s picture

I started working on this as well. I'm taking a different approach. I think it'll be easier to iteratively update the existing test until we've removed anything that would require the browser, and only then convert it to a Kernel test and leave in a browser test anything that requires the browser test.

Another thing I'm working on, is these embeds are a little hard to read because they are long strings, so they go out of the viewport in my IDE. So I want to use DomDocument to allow arrays of attributes to be used in the tests so they are more vertical and therefore easier to read.

-    $this->assertRaw('<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->assertHasAttributes($rendered_embed, [
+      'data-entity-type' => 'node',
+      'data-entity-id' => $this->node->id(),
+      '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',
+    ]);

This will come in handy when comparing variants with slight differences in attributes, I think.

Wim Leers’s picture

Issue tags: +Media Initiative

Many thanks, both of you!


#3:

I know, unit tests are hard when doing rendering!

  1. +++ b/tests/src/Kernel/EntityEmbedFilterMediaTest.php
    @@ -0,0 +1,164 @@
    +class EntityEmbedFilterMediaTest extends KernelTestBase {
    

    Great start, thanks!

  2. +++ b/tests/src/Kernel/EntityEmbedFilterMediaTest.php
    @@ -0,0 +1,164 @@
    +    // \Drupal\entity_embed\EntityEmbedDisplay\FieldFormatterEntityEmbedDisplayBase::build()
    +    // has an undeclared dependency on the "Node" module.
    +    'node',
    

    Reported independently, and being fixed over at #3054873: Hidden dependency on node module :)


#4: You may be right. I have no strong preference about how we get there. As long as we get there :)

I see what you're saying with the painful assertions. I encountered the same problem. That's why as part of #2752253-42: Document correct filter_html_image_secure + filter_align + filter_caption + entity_embed filters order, write tests, and ensure correct by default, I started converting those $this->assertRaw('LONG HTML STRING') calls to something like $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"]');. I like how you're taking it even further.


Does either of you have time to continue this in the next days or week?

oknate’s picture

I made a little more progress. While I haven't spent that much time on it, I got stuck on published vs not published and the caption display. Those are out of the scope of the filter, so my way of generating the markup for those assertions is too limited:

    /** @var \Drupal\filter\FilterProcessResult $filter_result */
    $filter_result = $this->filter->process($content, 'en');
    $output = $filter_result->getProcessedText();

We could possibly leave those ones in place when we move the others ones to a kernel test. I'm pretty busy the next week, but will try to work on it.

oknate’s picture

More progress on converting the existing tests to not use drupalGet(), so they'll be easier to convert to kernel tests.

Oh and I figured out the caption display. It's a separate filter. So I was able to convert those tests.

oknate’s picture

I realize that most of the existing tests are nodes. So we could convert them to embedding media instead, or duplicate them.

I also haven't added any more edge cases yet. So we'll have to scour the functionality of the module to find more edge cases to test.

Also, I believe @marcoscano's test is media specific, so we may want to end up with two test classes.

oknate’s picture

More progress removing getDrupal calls in the original test. Now all of those are gone from testFilter() method. There's still the testTranslation() method to do, and to convert this to a Kernel test.

oknate’s picture

Last patch (#9) was missing latest changes. Here's the corrected patch.

It looks like I reverted some changes from #3042600 when I merged those changes in, so I'll need to go back through the patch and fix that.

-    $this->assertSession()->responseNotContains('data-align="left"');
-    $this->assertSession()->responseNotContains('data-caption="test caption"');
+    $this->assertNoRaw('data-align="left"', 'Align data attribute not found.');
+    $this->assertNoRaw('data-caption="test caption"', 'Caption data attribute not found.');

After looking through the patch, this looks like the only place that happened. And this is because I had failed to convert this section over to the new test format.

oknate’s picture

Updated testTranslation() method to remove getDrupal calls.

oknate’s picture

Combining the two tests cuts the run time from 30 seconds to 17 seconds. With reducing runtime being the goal of a Kernel test, I'll combine them.

oknate’s picture

In regard to

embedded entity revisions

AFAIK, entity embed doesn't support embedding revisions.

Wim Leers’s picture

Issue summary: View changes

Woah, massive progress since I last reviewed this! 🥳 💪

#12:

With reducing runtime being the goal of a Kernel test, I'll combine them.

Kernel tests already run much faster than functional tests. Let's not combine them, because then we will eventually end up with a single multi-thousand line method that is impossible to understand. Let's keep them scoped as before.
EDIT: this actually will probably become irrelevant if we start using a @dataProvider like I suggested below :)

#13: you're right. Ignore that bullet :) Removed it.


  1. +++ b/tests/src/Functional/EntityEmbedFilterTest.php
    @@ -2,8 +2,11 @@
    @@ -27,6 +30,41 @@ class EntityEmbedFilterTest extends EntityEmbedTestBase {
    
    @@ -27,6 +30,41 @@ class EntityEmbedFilterTest extends EntityEmbedTestBase {
    +    $manager = $this->container->get('plugin.manager.filter');
    +    $bag = new FilterPluginCollection($manager, []);
    +    $this->filter = $bag->get('entity_embed');
    +    $this->filterCaption = $bag->get('filter_caption');
    +  }
    

    This has not yet been converted to a kernel test. Even though much of the setUp() method is already prepared for operating in a kernel test: it's very similar to \Drupal\Tests\filter\Kernel\FilterKernelTest::setUp(), where \Drupal\Tests\filter\Kernel\FilterKernelTest::testAlignFilter() and \Drupal\Tests\filter\Kernel\FilterKernelTest::testCaptionFilter() are good examples to consider.

  2. +++ b/tests/src/Functional/EntityEmbedFilterTest.php
    @@ -34,181 +72,388 @@ class EntityEmbedFilterTest extends EntityEmbedTestBase {
         // Tests entity embed using entity ID and view mode.
    ...
    +    $content = $this->getEmbedCode([
    +      'data-entity-type' => 'node',
    +      'data-entity-id' => $this->node->id(),
    +      'data-view-mode' => 'teaser',
    +    ]);
    ...
    +    $this->assertNotContains('drupal-entity data-entity-type="node" data-entity', $output);
    +    $this->assertNotContains('This placeholder should not be rendered.', $output);
    ...
         // Tests that embedded entity is not rendered if not accessible.
    

    This is now ideally prepared to be converted to a test using a @dataProvider. The quoted comment would be the array key (describing a particular test case) and the parameters for getEmbedCode() would be the first array value, plus additional array values for each of the expectations below.

    So something like

    /**
     * @dataProvider filterData
     */
    function testFilter(array $attributes, array $expected_html_present, array $expected_html_absent, $expected_attributes) {
      // assertions here
    }
    
    function filterData() {
      return [
        'the minimum: entity type, ID and view mode'=> [
          ['data-entity-type' => 'node', 'data-entity-id' => 3, 'data-view-mode' => 'teaser'],
          ['NODE BODY VALUE: hello world!'],
          [
            'drupal-entity data-entity-type="node" data-entity', 
            'This placeholder should not be rendered.',
          ],
          […]
        ],
        'not rendered if not accessible' => […],
        …
      ];
    }
    

    This will:
    A) allow each test case to run independently, meaning that if one of the earlier cases fails, the others are still evaluated, which makes the test suite MUCH more useful
    B) therefore it becomes 100x easier to debug regressions
    C) make it much easier to get a sense of the various test cases that exist

    Good examples of this pattern: \Drupal\Tests\big_pipe\Unit\Render\Placeholder\BigPipeStrategyTest and \Drupal\Tests\layout_builder\Kernel\DefaultsSectionStorageTest::providerTestAccess().

If you want, I'm happy to explain it more detail. :) Or start doing this for a subset of it.

oknate’s picture

I think I understand. So no more explanation needed. If you want to jump in and do the data provider yourself, that's cool. Otherwise, I'll take a stab at it. The part I had trouble with was converting it to a Kernel test. I think it might take me a few hours to figure out, and it was frustrating, so I haven't tried again yet. If you want to give it a go, or if you can convince a masochist like phenaproxima to figure that part out, that would be welcome. Although I'm willing to work on it, when I feel ready.

oknate’s picture

Now that I think about it, I think I should do the data provider stuff. It'll be good practice for me, as I haven't used data providers that much, and it would be a waste of your time, when you have all this amazing stuff to do with the javascript. So at least with the data provider stuff, I'll do it.

Wim Leers’s picture

🥳👍

Yeah I was in your place a few years ago, I first wasn't entirely convinced by its value, but once I got the hang of it, MAN IS IT AWESOME! Seems superfluous now, but wait until you run into a bug that breaks just this one edge case. It's such a time saver then.

WRT converting to kernel test: I'll try to do that today.

oknate’s picture

I started on the data provider. This is a work in progress. I'll work on it more over the weekend.

Wim Leers’s picture

#18 is looking excellent already! 👏

wannesdr’s picture

Assigned: Unassigned » wannesdr

I'm going to have a shot at this test.

wannesdr’s picture

I made a new patch where I restructured and split up the tests. Test dataproviders are all prefixed with 'provider'.
Some were too unique to put together with the rest so I created separate methods for them. If in the future they also need a dataprovider we can add them when necessary.

In my opinion: figcaption tests also deserve more attention so I inserted a placeholder for them already. They need to be moved from other tests, or created from scratch maybe.

wannesdr’s picture

Status: Needs work » Needs review
Wim Leers’s picture

Here is interim feedback, this is heading the right direction — thanks!

  1. +++ b/tests/src/Functional/EntityEmbedFilterTest.php
    @@ -76,11 +76,78 @@ class EntityEmbedFilterTest extends EntityEmbedTestBase {
    +   * Tests the entity_embed filter.
    

    Nit: Already in the class doc, can be removed :)

  2. +++ b/tests/src/Functional/EntityEmbedFilterTest.php
    @@ -76,11 +76,78 @@ class EntityEmbedFilterTest extends EntityEmbedTestBase {
    +   * @throws \Drupal\Core\Entity\EntityStorageException
    

    Nit: We don't add @throws in test methods :)

  3. +++ b/tests/src/Functional/EntityEmbedFilterTest.php
    @@ -76,11 +76,78 @@ class EntityEmbedFilterTest extends EntityEmbedTestBase {
    +  public function testFilter($embed_attributes, $contains, $not_contains, $expected_attributes, $published, $view_unpublished) {
    

    Nit: $embed_attributes is an array, so should be typehinted array.

  4. +++ b/tests/src/Functional/EntityEmbedFilterTest.php
    @@ -76,11 +76,78 @@ class EntityEmbedFilterTest extends EntityEmbedTestBase {
    +      $this->webUser->addRole('access_unpublished');
    

    Nit: $this->webUser can be a local variable, it's not reused anywhere.

  5. +++ b/tests/src/Functional/EntityEmbedFilterTest.php
    @@ -76,11 +76,78 @@ class EntityEmbedFilterTest extends EntityEmbedTestBase {
    +    if (!$published && !$view_unpublished) {
    

    The naming confused me; I think $is_published and $allowed_to_view_unpublished are clearer.

  6. +++ b/tests/src/Functional/EntityEmbedFilterTest.php
    @@ -76,11 +76,78 @@ class EntityEmbedFilterTest extends EntityEmbedTestBase {
    +    $this->assertNotContains('This placeholder should not be rendered.', $output);
    
    @@ -174,115 +241,163 @@ class EntityEmbedFilterTest extends EntityEmbedTestBase {
    +        'not_contains' => 'This placeholder should not be rendered.',
    ...
    +        'not_contains' => 'This placeholder should not be rendered.',
    

    These test case-specific not_contains cases are already tested automatically/generically, so there's no need to repeat them here.

  7. +++ b/tests/src/Functional/EntityEmbedFilterTest.php
    @@ -76,11 +76,78 @@ class EntityEmbedFilterTest extends EntityEmbedTestBase {
    +      if (\is_array($contains)) {
    ...
    +      if (\is_array($not_contains)) {
    

    Nit: the leading \ is unnecessary.

  8. +++ b/tests/src/Functional/EntityEmbedFilterTest.php
    @@ -174,115 +241,163 @@ class EntityEmbedFilterTest extends EntityEmbedTestBase {
    +      'Test deprecated \'default\' Entity Embed Display plugin.' => [
    

    Übernit: \' in there: you could use either backticks or wrap it in double quotes.

  9. +++ b/tests/src/Functional/EntityEmbedFilterTest.php
    @@ -174,115 +241,163 @@ class EntityEmbedFilterTest extends EntityEmbedTestBase {
    +      if (\is_array($contains)) {
    +        foreach ($contains as $needle) {
    +          $this->assertContains($needle, $output);
    +        }
    +      }
    +      else {
    +        $this->assertContains($contains, $output);
    +      }
    

    This is in ::testInvalidEntity() and is extremely similar to the above test method. Perhaps a helper method would simplify this. Or perhaps one of the test*() methods can call the other?

  10. +++ b/tests/src/Functional/EntityEmbedFilterTest.php
    @@ -174,115 +241,163 @@ class EntityEmbedFilterTest extends EntityEmbedTestBase {
    +  /**
    +   * Test that tag of container element is not replaced when it's not
    +   * <drupal-entity>.
    +   */
    +  public function testContainerTagElementWhenNot($tag = "drupal-entity") {
    ...
    +    $content = str_replace($tag, "not-{$tag}", $content);
    

    Why is there a parameter if this is not a data provider?

  11. +++ b/tests/src/Functional/EntityEmbedFilterTest.php
    @@ -174,115 +241,163 @@ class EntityEmbedFilterTest extends EntityEmbedTestBase {
    +    /** @var \DOMNode $figure_wrapper */
    

    Outdated annotation :)

  12. +++ b/tests/src/Functional/EntityEmbedFilterTest.php
    @@ -174,115 +241,163 @@ class EntityEmbedFilterTest extends EntityEmbedTestBase {
    +    $this->assertNotEmpty($untouched);
    

    What is this asserting? How can it be not empty?

wannesdr’s picture

FileSize
49.51 KB

Patch (should) now apply.
Will fix feedback from Wim in the next patch.

wannesdr’s picture

Update with the fixed feedback of Wim.
I will continue with refactoring later this morning.

Wim Leers’s picture

Status: Needs review » Needs work
  1. +++ b/tests/src/Functional/EntityEmbedFilterTest.php
    @@ -101,39 +96,21 @@ class EntityEmbedFilterTest extends EntityEmbedTestBase {
         // Clear caches.
    -    drupal_flush_all_caches();
    +    // drupal_flush_all_caches();
    

    AWESOME! Tests still passing with this commented out. So let's remove it now :)

  2. +++ b/tests/src/Functional/EntityEmbedFilterTest.php
    @@ -348,13 +322,13 @@ class EntityEmbedFilterTest extends EntityEmbedTestBase {
    +    $content = str_replace('drupal-entity', "not-drupal-entity", $content);
    

    Perhaps it'd be clearer to not use <not-drupal-entity> but $this->randomMachineName()? :)

  3. +++ b/tests/src/Functional/EntityEmbedFilterTest.php
    @@ -38,199 +48,490 @@ class EntityEmbedFilterTest extends EntityEmbedTestBase {
    +        'not_contains' => NULL,
    ...
    +        'not_contains' => NULL,
    ...
    +    if (!empty($not_contains)) {
    +      if (is_array($not_contains)) {
    +        foreach ($not_contains as $needle) {
    +          $this->assertNotContains($needle, $output);
    +        }
    +      }
    +      else {
    +        $this->assertNotContains($not_contains, $output);
    +      }
    

    This is actually fairly confusing. Because we allow NULL or array, not only are the test cases more difficult to grok ("what is the meaning of NULL here?"), it also makes for more complex assertion logic. The assertion logic first needs to check the type of the argument, whereas if it'd just always be array, we could do this:

    foreach ($not_contains as $needle) {
      $this->assertNotContains(…);
    }
    

    That'd be it!

    In the test cases, we'd then have [] instead of NULL, which is more clear: there are no strings whose non-existence needs to be asserted.

  4. I will continue with refactoring later this morning.

    How is this going? :)

wannesdr’s picture

Inbetween update of the patch.
I move the filterTranslations to a different test entirely and refactored it to use one dataProvider.
I also further refactored the filterTests.

What definitely needs to be done next is check which methods in the EntityEmbedTestBase are unique or shared between EntityEmbedFilterTest and EntityEmbedFilterTranslationTest and move them accordingly. And also cleanup obsolete code in any of those three classes.

To be continued...

Status: Needs review » Needs work

The last submitted patch, 27: 3052482-27.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

wannesdr’s picture

Status: Needs work » Needs review
FileSize
54.32 KB
1.06 KB

I fixed the issue that made the test fail. I changed the testInvalidEntity dataProvider so the $contains and $not_contains are also arrays.

Status: Needs review » Needs work

The last submitted patch, 29: 3052482-28.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

wannesdr’s picture

Status: Needs work » Needs review
FileSize
54.4 KB
539 bytes

I forgot to re-add the TranslatableInterface RenderContext use statements ofter they moved around a bit.

wannesdr’s picture

FileSize
8.54 KB
54.1 KB

Fixed the patch. Helper functions moved to the right class and cleaned some of the CS warnings in de test run.

Status: Needs review » Needs work

The last submitted patch, 32: 3052482-32.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

wannesdr’s picture

Status: Needs work » Needs review
FileSize
1.11 KB
54.13 KB

Fixed the testContainerTagElementReplace to use randomMachineName().

Status: Needs review » Needs work

The last submitted patch, 34: 3052482-34.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

wannesdr’s picture

Apparently the randomMachineName() generator also uses capital letters, this is not allowed for a tag and therefore the test fails.
I'm going to replace the randomMachineName() with just a hardcoded string.

Also: https://www.drupal.org/project/drupal/issues/2571183, so since this may be deleted once it's a good thing not to use it in our test.
Thanks to @borisson_ for the issue nr.

wannesdr’s picture

Status: Needs work » Needs review
FileSize
1.54 KB
54.14 KB

New patch to remove the randomMachineName() from comment #36.

wannesdr’s picture

FileSize
53.77 KB
1.73 KB

Yay! Finally green test results!

Here is a new patch with the final coding standards messages fixed.

borisson_’s picture

Status: Needs review » Needs work

Nitpicks ahead.

  1. +++ b/tests/src/Functional/EntityEmbedFilterTest.php
    @@ -31,6 +28,13 @@ class EntityEmbedFilterTest extends EntityEmbedTestBase {
    +   * The renderer Service.
    

    Service doesn't have to be capitalized here.

  2. +++ b/tests/src/Functional/EntityEmbedFilterTest.php
    @@ -38,304 +42,514 @@ class EntityEmbedFilterTest extends EntityEmbedTestBase {
    +    $this->renderer = $this->container->get('renderer');
    +
    

    Unneeded blank line.

  3. +++ b/tests/src/Functional/EntityEmbedFilterTest.php
    @@ -38,304 +42,514 @@ class EntityEmbedFilterTest extends EntityEmbedTestBase {
    +  public function assertContainsHelper(array $contains, array $not_contains, $output) {
    

    Helper functions don't need to be public - can be protected.

  4. +++ b/tests/src/Functional/EntityEmbedFilterTest.php
    @@ -38,304 +42,514 @@ class EntityEmbedFilterTest extends EntityEmbedTestBase {
    +  public function testInvalidEntity(array $embed_attributes, array $contains, array $not_contains) {
    ...
    +  public function providerTestInvalidEntity() {
    

    This has a dataProvider, but only one value in the dataprovider. I don't think it's needed to already use the dataprovider if it doesn't gain anything of value.

  5. +++ b/tests/src/Functional/EntityEmbedFilterTest.php
    @@ -38,304 +42,514 @@ class EntityEmbedFilterTest extends EntityEmbedTestBase {
    +  public function testFigCaption() {
    +
    +  }
    ...
    +  /**
    +   * Data provider for testFigCaption.
    +   */
    +  public function providerTestFigCaption() {
    +    return [];
    +  }
    

    These can be removed, I think?

  6. +++ b/tests/src/Functional/EntityEmbedFilterTranslationTest.php
    @@ -0,0 +1,196 @@
    +   * The renderer Service.
    

    ^

  7. +++ b/tests/src/Functional/EntityEmbedTestBase.php
    @@ -102,14 +101,14 @@ abstract class EntityEmbedTestBase extends BrowserTestBase {
    +    // This has to be hard-coded for use in data providers,
    +    // are evaluated before setUp().
    

    this is missing words? a "because" after the comma would make this more readable.

wannesdr’s picture

Status: Needs work » Needs review
FileSize
4.83 KB
53.55 KB

Thanks @borisson_ for the feedback!
Attached is a new patch with the corrections. I made all the helper functions protected and also moved one to the bottom of the EntityEmbedFilterTest, seemed better that way.

Wim Leers’s picture

Been caught up in meetings all day. Reviewed & committed two CKEditor-related patches today; this is my top priority for tomorrow 🥳

oknate’s picture

Great!

I can think of some edge cases to add after the last round of bug fixes.

  • caption
  • caption with link
  • align left
  • align center
  • align right
  • It seems there are some legacy properties, such as data-view-mode and data-entity-id. It would be great if that were documented, as far as named test cases marked as legacy support.
  • The data-entity-embed-display-settings property doesn't seem to apply to view mode derived embeds. It would be good to get some clarity as to which supercedes which. I haven't tested, but I would assume that if you're using a view mode derived formatter, there are basically no settings, and therefore, no settings added there should take affect.

Perhaps it's redundant, but we could also test the alt and title overrides here.

Wim Leers’s picture

Interestingly, I have some fails locally, with PHP 7.2.14:

Testing Drupal\Tests\entity_embed\Functional\EntityEmbedFilterTranslationTest
.....                                                               5 / 5 (100%)

Time: 44.86 seconds, Memory: 6.00MB

OK (5 tests, 48 assertions)

Testing Drupal\Tests\entity_embed\Functional\EntityEmbedFilterTest
..........SEEE..                                                  16 / 16 (100%)

Time: 1.97 minutes, Memory: 6.00MB

There were 3 errors:

1) Drupal\Tests\entity_embed\Functional\EntityEmbedFilterTest::testDisplayPluginPreference
Error: Call to undefined method DOMText::getAttribute()

/Users/wim.leers/Work/d8/modules/entity_embed/tests/src/Functional/EntityEmbedFilterTest.php:668
/Users/wim.leers/Work/d8/modules/entity_embed/tests/src/Functional/EntityEmbedFilterTest.php:398

2) Drupal\Tests\entity_embed\Functional\EntityEmbedFilterTest::testAttributesWhenUsingImageFormatter
Error: Call to undefined method DOMText::getAttribute()

/Users/wim.leers/Work/d8/modules/entity_embed/tests/src/Functional/EntityEmbedFilterTest.php:668
/Users/wim.leers/Work/d8/modules/entity_embed/tests/src/Functional/EntityEmbedFilterTest.php:451

3) Drupal\Tests\entity_embed\Functional\EntityEmbedFilterTest::testEmbedSettingsCompatibility
Error: Call to undefined method DOMText::getAttribute()

/Users/wim.leers/Work/d8/modules/entity_embed/tests/src/Functional/EntityEmbedFilterTest.php:668
/Users/wim.leers/Work/d8/modules/entity_embed/tests/src/Functional/EntityEmbedFilterTest.php:492

This is caused by >firstChild returning a "text" DOM node for whitespace.

I wonder if this is specific to PHP 7.2, so I queued a test for it: https://www.drupal.org/pift-ci-job/1324414. That passed too. So … I don't know what's going on here, but let's just work around the problem by making the test coverage more robust :)

oknate’s picture

I believe DOMNode doesn't have getAttribute but inherits it from DOMElement, the problem is it not every DOMNode is a DOMElement, so we'd need to check if it is. Why the test fails locally, I don't know.

oknate’s picture

This task is getting pretty large and unfocused. Perhaps we should commit an intermediary first step and create follow ups for
1) Converting to Kernel test
2) Specific edge cases.

Or just commit an intermediary step for the progress so far and leave this issue open.

oknate’s picture

When I say "unfocused", specifically I refer to this:

test many more edge cases

This could go for a long time. So I think it would be great to get the progress so far committed, and then we continue with this.

Wim Leers’s picture

Actually, to make iterating on this faster, I spent a few hours today on making this a kernel test. The additional set of changes is minimal. It's mostly in ::setUp(). Which means it's much easier to review and understand than all of the work that has happened on this issue so far.

The difference is quite big:

Before
Testing Drupal\Tests\entity_embed\Functional\EntityEmbedFilterTest
..........S.....                                                  16 / 16 (100%)

Time: 1.77 minutes, Memory: 6.00MB

OK, but incomplete, skipped, or risky tests!
Tests: 16, Assertions: 256, Skipped: 1.
Testing Drupal\Tests\entity_embed\Functional\EntityEmbedFilterTranslationTest
.....                                                               5 / 5 (100%)

Time: 35.85 seconds, Memory: 6.00MB

OK (5 tests, 48 assertions)
After
PHPUnit 6.5.14 by Sebastian Bergmann and contributors.

Testing Drupal\Tests\entity_embed\Functional\EntityEmbedFilterTest
..........S.....                                                  16 / 16 (100%)

Time: 19.85 seconds, Memory: 6.00MB

OK, but incomplete, skipped, or risky tests!
Tests: 16, Assertions: 302, Skipped: 1.
PHPUnit 6.5.14 by Sebastian Bergmann and contributors.

Testing Drupal\Tests\entity_embed\Functional\EntityEmbedFilterTranslationTest
.....                                                               5 / 5 (100%)

Time: 6.91 seconds, Memory: 6.00MB

OK (5 tests, 53 assertions)

Next up: extracting common logic in a base test class for all filter kernel tests. And then actually reviewing the changes.

In converting this to kernel tests, a few assumptions in the functional tests were made more explicit, which is an expected and welcome consequence 🙂

oknate’s picture

Wow, nicely done. I'm looking forward to reviewing how you did it. I tried a few weeks ago and I got stuck.

Wim Leers’s picture

Before extracting a base test class: moving to the right location.

oknate’s picture

1) Missing space before docblock.

diff --git a/tests/src/Kernel/EntityEmbedFilterTest.php b/tests/src/Kernel/EntityEmbedFilterTest.php
index 38f4e67..d04fe71 100644
--- a/tests/src/Kernel/EntityEmbedFilterTest.php
+++ b/tests/src/Kernel/EntityEmbedFilterTest.php
@@ -167,6 +167,7 @@ class EntityEmbedFilterTest extends KernelTestBase {
       $this->assertFalse($rendered_embed->hasAttribute('data-entity-id'));
     }
   }
+
   /**
    * Data provider for testFilter.
    */

2) For drupalCreateNode, can we use one array, as we do in other tests?

    // Create a sample node to be embedded.
    $this->node = $this->drupalCreateNode([
      'type' => 'page',
      'title' => 'Embed Test Node',
      'body' => [
        'value' => 'This node is to be used for embedding in other nodes',
      ],
      'uuid' => 'e7a3e1fe-b69b-417e-8ee4-c80cb7640e63',
    ]);

3) Why no param descriptions? The coder linter didn't flag them either. Are they optional for tests? As they are self-describing, I don't think we need them, if it's not a coding standard violation.

  /**
   * 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) {

4) no param descriptions for testInvalidEntity() too? See #3.
5) This text:

Test if tag of container element remains when not .

Is a bit confusing. Perhaps "Test HTML tag with same attributes as drupal-entity not affected."

6) in testOverridesAndRenderCaching() we create a media entity. Maybe this should be moved to setUp() so it can be reused.
7) I'm not a fan of this function:


  /**
   * Helper function to assert contains and notContains.
   */
  protected function assertContainsHelper(array $contains, array $not_contains, $output) {
    if (!empty($contains)) {
      foreach ($contains as $needle) {
        $this->assertContains($needle, $output);
      }
    }
    if (!empty($not_contains)) {
      foreach ($not_contains as $needle) {
        $this->assertNotContains($needle, $output);
      }
    }
  }

I think it's not readable and self-describing, I think it should be two functions, assertContainsMultiple() and assertNotContainsMultiple().

oknate’s picture

Changed #1, #2 and #5. If I get a chance tonight, I'll look at #7.

Status: Needs review » Needs work

The last submitted patch, 51: 13151825-49.patch, failed testing. View results

oknate’s picture

Status: Needs work » Needs review
FileSize
1.65 KB
66.23 KB

Fixes missing period.

oknate’s picture

Replacing assertContainsHelper() with assertContainsMultiple() and assertNotContainsMultiple(). Purely cosmetic. I just think it's easier to read this way.

oknate’s picture

Title: Expand EntityEmbedFilter's test coverage: test many more edge cases, hopefully change to kernel test » Expand EntityEmbedFilter's test coverage: test many more edge cases, change to kernel test

Dropping the word "hopefully" from title in reference to Kernel test conversion. Nicely done, Wim.

oknate’s picture

Assigned: wannesdr » Unassigned
FileSize
19.65 KB
64.17 KB

Adding base class EntityEmbedKernelTestBase.php and updating EntityEmbedFilterTranslationTest and EntityEmbedFilterTest to extend it.

oknate’s picture

Issue summary: View changes
oknate’s picture

Issue summary: View changes

Status: Needs review » Needs work

The last submitted patch, 56: 13151825-56.patch, failed testing. View results

oknate’s picture

Status: Needs work » Needs review
FileSize
1.42 KB
64.64 KB

I guess that admin user was used after all. PHP storm was indicating it as an unused variable.

Wim Leers’s picture

Assigned: Unassigned » Wim Leers
FileSize
13.33 KB
58.73 KB

This then extracts a base class doing the absolute minimum. We don't want the base test class to be coupled to all the potential things we want to have tests for; we want each subclass to install the additional modules needed and set up the additional configuration needed. That way, it remains very clear what the dependencies are, and adding new capabilities then also is forced to provide correspondingly clear test coverage.

EDIT: oh wow, @oknate continued working on this too. My bad, I should've assigned to myself. No big deal though — I'll incorporate his changes :) That means only #56 will largely be wasted. Assigning to myself, as I should've done before.

To be crystal clear: the attached interdiff is relative to my last patch (#48).

Wim Leers’s picture

  • #51 + #53: see attached interdiff-ported-51_and_53.txt.
  • #54: see attached interdiff-ported-54.txt. Also: 👏👏 — that's exactly what I was going to suggest to @Wannes DR in a next iteration :)
  • #55: Thanks 😊
  • #56: see #61, where I already did the same, but in a more strict fashion (bare minimum base test class). I see you moved a bunch of the helpers into the base class. I did not yet do that. I prefer to hold off on doing that until there is a proven need for it across multiple test subclasses.
  • #60: I already took care of this in #61, where I also added a comment explaining why this is necessary. Your guess at the root cause in #60 is reasonable, but not correct. The real answer lies in \Drupal\user\Entity\User::hasPermission() 🤓
Wim Leers’s picture

The current test coverage is specifically testing Classy's markup and its subthemes. We shouldn't do that.

(It is currently not assuming core/modules/filter/templates/filter-caption.html.twig but core/themes/classy/templates/content-edit/filter-caption.html.twig.)

Wim Leers’s picture

This test refactor is currently leading us into a kinda awkward place. We're not testing @Filter=entity_embed in isolation: we're testing it together with @Filter=filter_caption. Ideally we'd test it in isolation. But

In HEAD:

    $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"]');

In this patch, that is converted to:

    $rendered_embed = $xpath->query('//figure[@role="group"]/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',
    ]);
    $figcaption = $xpath->query('//figcaption')[0];
    $this->assertNotEmpty($figcaption);
    $this->assertEquals('test caption', $figcaption->textContent);

This is asserting the results for @Filter=filter_caption, but not for @Filter=filter_align. Having integration tests makes sense.

Note that this also lost the test coverage that asserts whether the alt and title on <drupal-entity> have been persisted onto the generated <img> tag. And it in fact is not testing anything on the <img> tag at all anymore, which git blame shows was the goal in #2712111: Embed specific attributes are cached along with the entity, where this test coverage was originally added.

I also have my doubts about whether this is truly clearer. I wrote in #5 how I like that you're taking it further, but it never was taken all the way, and hence is currently resulting in a net reduction in test coverage. This particular bit of test coverage was lost in #7's interdiff.

I'll continue to evaluate this as I review the rest of the patch.

Wim Leers’s picture

Here is another concrete example of where the conversion is wrong: \Drupal\Tests\entity_embed\Kernel\EntityEmbedFilterTest::testDisplayPluginPreference(). This is porting the test coverage added by https://git.drupalcode.org/project/entity_embed/commit/b9a4f65c88b75cf3d.... It's asserting the presence of data- attributes continuing to exist that we set ourselves (which is pointless, of course they continue to exist), and is testing caption filter integration (which is not the edge case this is aiming to test). Reducing this back to the original scope make this much clearer.

Wim Leers’s picture

And another such example: testEmbedSettingsCompatibility(). This used to and still should be testing that no <a> was created, since that's what the legacy data-embed-settings attribute is specifying. But that's not being asserted at all anymore.

(This was introduced by #2760801: Rename data-entity-embed-settings attribute — see https://git.drupalcode.org/project/entity_embed/commit/72a93e2d5efa7a760....)

Wim Leers’s picture

These $this->assertNotContains('This placeholder should not be rendered.', $output); assertions all over the place were annoying, distracting and confusing in HEAD, and they still are. Let's make that better.

oknate’s picture

Excellent finds in #64, #65 and #66. That's the danger with changing lots of things at once. Some things tend to get lost and overlooked in the shuffle. Thanks for double checking these still do what they were originally intended to do!

The last submitted patch, 66: 3052482-66.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 67: 3052482-67.patch, failed testing. View results

Wim Leers’s picture

#68: indeed! An unavoidable painful process to go through when taking code with a lot of history but without clear test coverage. None of this is a criticism on either you or the original code contributors — we're all bound to deadlines, we all learn along the way, we all would like to do better. I just am fortunate enough to be given the task to bring this to core and have gone through stabilization processes like this many times before :) Please continue to double-check what I'm doing, and let me know when you disagree (or when you're happy with a change!).

Wim Leers’s picture

Status: Needs work » Needs review
FileSize
838 bytes
57.32 KB

Looks like there was a PHP 7.2-specific assertion.

oknate’s picture

I’ll double check the progress later today. I had to fix an issue for another project this morning.

Wim Leers’s picture

Just like #65 and #66, here's another correction, this time for testAttributesWhenUsingImageFormatter(). This is the one I was talking about at length in #64.

Wim Leers’s picture

Let's remove the dependency of our kernel test on entity_embed_test, which installs boatloads of configuration, so it's not at all clear what exactly we're testing with.

(entity_embed_test sort of became a dumping ground, which was fine for fast iteration, but if we want to bring this to core, we'll need more rigor.)

Wim Leers’s picture

#75 allows us to remove editor and ckeditor from the modules being installed. We're getting to a place where dependencies are actually clear! 🥳

Wim Leers’s picture

Related to #74: the cacheability-related assertions in #2712111: Embed specific attributes are cached along with the entity are actually covered much better by #3058872: Entity Embed render array shouldn't cache separately from host field, which introduced testOverridesAndRenderCaching().

That being said, testAttributesWhenUsingImageFormatter() is still valuable to keep: it asserts that overrides are working for File entities plus @EntityEmbedDisplay=image:image, whereas testOverridesAndRenderCaching() is testing Media entities plus @EntityEmbedDisplay=view_mode:*.

I think it makes sense to test both of those "override data of the embedded entity" scenarios in a single test class. #75 was a necessary step to make that feasible, because we don't want to be changing test logic and moving in a single interdiff, that makes it much to hard to track.

So this interdiff moves the two aforementioned test methods to a new \Drupal\Tests\entity_embed\Kernel\EntityEmbedFilterOverridesTest class, renames the methods and improves their docs, but doesn't change anything about their code. A happy (and very much intentional) consequence is that the file, image and media modules no longer need to be installed for EntityEmbedFilterTest, and almost no configuration needs to be installed anymore! 💪 That means that just like the name indicates, this is now providing the foundational tests only, without testing integration with lots of other modules, entity types, and whatnot.

Simultaneously, I've moved assertHasAttributes() and drupalCreateRole() into the base class, because there are now multiple test classes using them.

Wim Leers’s picture

--- a/tests/src/Functional/EntityEmbedTestBase.php
+++ b/tests/src/Functional/EntityEmbedTestBase.php

The changes made to this test class are now irrelevant. Let's simply drop those hunks from the patch.

Wim Leers’s picture

Having looked at the override tests in detail for #77, I spotted a few problems:

  1. we're not yet testing what happens when there's no override
  2. there's a lot of attributes irrelevant to the test coverage, which make it needlessly hard to understand what the test is testing
  3. there's a useless drupalCreateNode()

Fixed all.

Wim Leers’s picture

And another problem in that same test, but which I wanted to post separately to keep scope clear: in one of the two test methods, we're using the existing trait, but wrapping it in a helper of our own. In the other test method, we're copying an existing file in core. We should make this consistent and less complex.

Wim Leers’s picture

I spotted $this->assertNotContains('drupal-entity data-entity-type="node" data-entity', $output); in a few places, this should be merged into assertEntityEmbedFilterHasRun(). That assertion method was added in #67 and this addition makes it much more clear what it does. It also helps that we're now consistently asserting this, instead of only in two places — that made it extra confusing!

Wim Leers’s picture

An assortment of clarity nitpicks:

  • data-align and data-caption where they're not needed
  • $rendered_embed = $xpath->query('//div[contains(@class, "embedded-entity")]') where that is not the rendered embed, but the container of the rendered embed — renamed all of them to embedded_entity_container

With these changes, I consider \Drupal\Tests\entity_embed\Kernel\EntityEmbedFilterOverridesTest core-worthy now.

Wim Leers’s picture

Assigned: Wim Leers » Unassigned

I'll continue iterating on EntityEmbedFilterTest because that definitely still needs more love. But I'm approaching my end of day. @oknate or @Wannes, feel free to port the @Filter=filter_caption and @Filter=filter_align test coverage from before that is currently missing. The testFigCaption() placeholder that Wannes added would be a great place to start to see those appear in :)

oknate’s picture

Awesome progress, I'll take a look later today. When talking about moving into core, the plan isn't to move the entity embed filter into core, but a close cousin of it just for media, right? Is the plan then to have the entity embed filter extend that? Or to have them both extend a base filter?

Wim Leers’s picture

Assigned: Unassigned » Wim Leers

I started working on this again. Started the process of comparing the old test coverage with the refactored test coverage, to ensure we don't lose any coverage.

Wim Leers’s picture

Updating testMissingEntityPlaceholder() to not create its own node but reuse the existing one, and to maximally use assertHasAttributes() rather than using that and doing raw string presence assertions.

Wim Leers’s picture

There is a lot of repetition of a few lines of code. Let's introduce a helpful helper and let it absorb assertEntityEmbedFilterHasRun():

-    /** @var \Drupal\filter\FilterProcessResult $filter_result */
-    $filter_result = $this->filter->process($content, 'en');
-    $output = $filter_result->getProcessedText();
-    $this->assertEntityEmbedFilterHasRun($output);
+    $output = $this->applyFilter($content);
oknate’s picture

Nice idea in #87!

Was this meant to go in?

@@ -295,6 +289,10 @@ class EntityEmbedFilterTest extends EntityEmbedFilterTestBase {
 
     $filter_result = $this->filter->process($content, 'en');
     $output = $filter_result->getProcessedText();
+    // 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->assertContains('This placeholder should not be rendered.', $output);
     $this->assertNotContains($this->node->body->value, $output);
 
     $dom = Html::load($output);

It looks like it might be reverting part of #67.

Actually, it looks like the comment is new. So nevermind.

Wim Leers’s picture

#88: yep, that's just a new comment and adding one assertion to be more explicit about what it's testing. That's the only case that cannot be converted precisely because it is not testing <drupal-entity> :)


If you liked #87, I think you'll like this too :)

We have a lot of parsing all over the place. So much of this:

    $dom = Html::load($output);
    $xpath = new \DOMXPath($dom);
    $this->assertSame(0, $xpath->query('//div[contains(@class, "embedded-entity")]/a')->length);

But \Drupal\KernelTests\AssertContentTrait is already used by KernelTestBase, so using cssSelect() is within reach!

This means assertHasAttributes() has to change slightly, because AssertContentTrait unfortunately uses SimpleXML for parsing, not DOMDocument. But I think that's okay. This again makes the tests simpler to understand.

Wim Leers’s picture

Fixed a few nits, plus:

  1. +++ b/tests/src/Kernel/EntityEmbedFilterTest.php
    @@ -0,0 +1,459 @@
    +        'not_contains' => [
    +          '<drupal-entity data-entity-type="node" data-entity',
    +        ],
    

    As of #81, this is handled automatically by applyFilter().

  2. +++ b/tests/src/Kernel/EntityEmbedFilterTest.php
    @@ -0,0 +1,459 @@
    +    $this->assertNotContains('Deleted content encountered, site owner alerted', $output);
    

    Rather than asserting the absence of this content, it's better to assert the absence of the placeholder element, because further in the test case we assert the presence of that placeholder element (after deleting the embedded entity).

oknate’s picture

Re #89, that's a lot less verbose!

-    $dom = Html::load($output);
-    $xpath = new \DOMXPath($dom);
-    /** @var \DOMNode $embedded_entity_container */
-    $embedded_entity_container = $xpath->query('//div[contains(@class, "embedded-entity")]')[0];
-    $this->assertHasAttributes($embedded_entity_container, [
+    $this->assertHasAttributes($this->cssSelect('div.embedded-entity')[0], [

Nice refactor . I don't know this cssSelect I'll take a look.

Wim Leers’s picture

#87 updated almost everything, with the exception of EntityEmbedFilterTranslationTest::testFilterTranslations() Let's update that too.

Wim Leers’s picture

So #92 did quite a bit of this:

+++ b/tests/src/Kernel/EntityEmbedFilterTranslationTest.php
@@ -47,12 +47,10 @@ class EntityEmbedFilterTranslationTest extends EntityEmbedFilterTestBase {
-    $this->assertContains($expected_title, $output);
...
+    $this->assertRaw($expected_title);

That pattern of swapping assertContains() for assertRaw() can be done in more places. And when that's done, applyFilter() no longer needs to return the resulting string.

This does that.

oknate’s picture

Cool, I didn't know you could do this:
+ $this->setRawContent($output);

It looks like this is Kernel test specific.

Wim Leers’s picture

#94: That's from \Drupal\KernelTests\AssertContentTrait.


Following up on #92: #27 introduced $assert_title_from_source, $assert_title_lang, $assert_extra and friends, and I think @Wannes DR perhaps thought it's more complicated than it is — this can be simplified a lot :)

Wim Leers’s picture

I've also finished comparing the test coverage with this patch applied compared to HEAD's test coverage. Nothing is missing except for the implicit integration test coverage with the @Filter=filter_align and @Filter=filter_caption filters. Hurray! 🥳

Thanks to both @oknate and @Wannes DR, I was able to approach this was a fresher perspective than I otherwise would have. Both of your suggestions, ideas and contributions are still present and will continue to live on. Hopefully you've both appreciated my taking the time to post piece-by-piece improvements. I am still all ears to hear suggestions from you too 🙂

I will continue working on those in a few hours.

borisson_’s picture

Hopefully you've both appreciated my taking the time to post piece-by-piece improvements. I am still all ears to hear suggestions from you too 🙂

I am super happy about this approach, I've learned a lot by watching you break up all these changes into logical units. Thank you! 👍

oknate’s picture

I totally appreciate the play-by-play. I've learned quite a bit too. I also have learned sometimes it's better to post three patches with three groups of changes rather than to "save space" on the issue. It makes it much easier to explain your changes.

Wim Leers’s picture

Issue summary: View changes
FileSize
9.46 KB
59.95 KB

#97: That's wonderful to hear :) Made my day — thanks! 🥳🤓


One of the crucial things that are still missing (and are also missing from HEAD) is asserting the presence of the expected bubbleable metadata. The additional assertions are trivial, but the code to properly run filters and collect bubbleable metadata (cacheability & attachments) is not.

Wim Leers’s picture

What I didn't tell you in #99 is that this also happens to be the infrastructure I need to be able to add caption filter integration tests 🤫😇

This test is pretty rough, with lots of copy/pasted bits, but they'll soon evaporate! 🌞

Wim Leers’s picture

+++ b/tests/src/Kernel/EntityEmbedFilterTest.php
@@ -0,0 +1,496 @@
+    $result = $this->applyFilter($content);
...
+    $this->applyFilter($content);
...
+    $filter_result = $this->filter->process($content, 'en');
+    $output = $filter_result->getProcessedText();
+    $this->setRawContent($output);

Three different calls, because three different needs. That's pretty awkward.

Fortunately #99 gave us the infrastructure to make this better 🥳

Wim Leers’s picture

+++ b/tests/src/Kernel/EntityEmbedFilterTestBase.php
@@ -98,14 +84,10 @@ abstract class EntityEmbedFilterTestBase extends KernelTestBase {
-      'uuid' => 'e7a3e1fe-b69b-417e-8ee4-c80cb7640e63',
+      'uuid' => static::EMBEDDED_ENTITY_UUID,

This hunk shouldn't have been in there, that's for a future iteration…

Wim Leers’s picture

Part 1 of #102's promised evaporation delivered.

Wim Leers’s picture

  1. Polish caption filter integration test coverage.
  2. Remove $this->renderer which is pointless as of a few patches ago.
Wim Leers’s picture

Part 2 of #102's promised evaporation delivered.

Now we have integration test coverage for filter_align, filter_caption and both simultaneously. 👍

Status: Needs review » Needs work

The last submitted patch, 105: 3052482-105.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Wim Leers’s picture

Status: Needs work » Needs review
FileSize
9.35 KB
61.93 KB

data-entity-id is deprecated, but the tests would make you think otherwise. This is a problem for future maintenance.

(Part of this already snuck in #105 and hence the failure.)

Wim Leers’s picture

Issue summary: View changes

It's very late. Time to wrap up my day. I'll continue tomorrow and expect to be able to commit this tomorrow afternoon my time.

(There is still some test refactoring that I think needs to happen. But the end is near!)


Added missing tests I'm aware of to the issue summary.

#42:

Great!

I can think of some edge cases to add after the last round of bug fixes.

  • caption
  • caption with link
  • align left
  • align center
  • align right
  • It seems there are some legacy properties, such as data-view-mode and data-entity-id. It would be great if that were documented, as far as named test cases marked as legacy support.
  • The data-entity-embed-display-settings property doesn't seem to apply to view mode derived embeds. It would be good to get some clarity as to which supercedes which. I haven't tested, but I would assume that if you're using a view mode derived formatter, there are basically no settings, and therefore, no settings added there should take affect.

Perhaps it's redundant, but we could also test the alt and title overrides here.

  • Irrelevant here; that's a concern of the caption filter.
  • See next.
  • ✅ — testing with either of the three is sufficient, everything else is a concern of the align filter.
  • ✅ on data-entity-id, I actually just did that 😀Finally, data-view-mode definitely is not deprecated. Why did you think that?
  • Hm, can you clarify this?
Wim Leers’s picture

Assigned: Wim Leers » Unassigned

If anybody wants to work on this in the mean time, feel free to. I'll continue in ~9 hours.

oknate’s picture

Thanks, Wim. Today was epic. I'll review if possible tonight!

Wim Leers’s picture

Assigned: Unassigned » Wim Leers
Wim Leers’s picture

Simplified EntityEmbedFilterTranslationTest::testFilterTranslations() — most of the embed attributes are the same across all test cases, so there's no need to repeat them many times, because then it's no longer clear what exactly we're testing (what input is changing and whose effect we're asserting).

Wim Leers’s picture

#112 was a fine improvement, but while working on it I spotted a problem that I apparently gradually introduced and made worse: in #87 I stopped passing the $process_lang input to the filtering. This should have broken tests, but did not, because the assertions were wrong.

Then in #95, I made that worse, by removing the $process_lang parameter altogether, since it seemed to have no effect. Well, it did not have any effect, otherwise #87 would've failed, but in doing so we lost about half of what we're testing!

I only found this thanks to simplifying (in #112) and then writing corresponding clear test case labels. This makes it easier to understand, but also helps spotting mistakes, as I've just proven :D

Wim Leers’s picture

A cohesive single test method and data provider are now within reach. 🥳

The end result after these last few iterations is complete test coverage (better than in HEAD!) and easier to understand tests.

Wim Leers’s picture

Issue summary: View changes
FileSize
2.98 KB
61.25 KB

And finally, cacheability assertions (only the relevant one: "does this trigger variations or not?") and some clean-up. I now consider EntityEmbedFilterTranslationTest to be core-worthy, just like I considered EntityEmbedFilterOverridesTest core-worthy since #82.

Wim Leers’s picture

We lost the test coverage that ensures that cacheability of entity access is bubbled up, which I helped write nearly three years ago: #2593379: Analyze/improve/fix handling of bubbleable metadata in entity_embed 😲

(Also, thanks to the refactored tests it's now very easy to observe the impact!)

Wim Leers’s picture

Round of clean-up. Fixes nits. Fixes coding standards violations. Removes the now unnecessary loadEntityByAttributes() helper.

Wim Leers’s picture

The accidental inclusion of a hunk mentioned in #102 is now actually intentional :)

Wim Leers’s picture

  1. Clean up labels for providerTestFilter() test cases.
  2. Extracted one of those into a legacy test: testEntityEmbedDisplayDefaultBackwardsCompatibility().
  3. Renamed testContainerTagElementReplace() and simplified it a lot.
Wim Leers’s picture

Moved all @group legacy tests to a new \Drupal\Tests\entity_embed\Kernel\EntityEmbedFilterLegacyTest. That represents test coverage that is 100% technical debt in the filter.

Wim Leers’s picture

The idea behind not_contains was good, but turns out we have no uses for it, at least not anymore, after all other refactoring. So let's remove it.

Wim Leers’s picture

Similarly, contains was a good idea. But now it's only being used for one kind of thing (many times):

        'contains' => [
          'This node is to be used for embedding in other nodes.',
        ],

That's the value of the body field of the sample entity used for embedding. Put differently: it's asserting that the embedded entity is actually rendered.

Rather than a highly abstract "contains nothing" (when the entity is inaccessible) or "contains this particular string" (when it is accessible and hence rendered), let's make that more clear.

While doing this, I also made this change:

 -    if (!$is_published && !$allowed_to_view_unpublished) {
+    if (!$rendered) {

The deleted line hardcoded knowledge about the expected behavior, rather than it merely asserting what it is being told to assert. Easily introduced, hard to realize why a test is complex :)

Wim Leers’s picture

More than half of the complexity of testFilter() (at least in terms of function arguments and hence information per test case in the provider) is for the "access allowed or denied" test cases.

That's a strong indicator that should be extracted to its own test method plus data provider; that should make things a lot simpler.

Wim Leers’s picture

#123 renamed testFilter() to testBasics(). Time to review that one last time.

  • The data-entity-embed-display + data-view-mode ⇒ data-entity-embed-display wins test case in the testBasics() data provider means testEntityEmbedDisplayAttributeVersusViewModeAttribute() is a mere duplicate, so we can remove it.
  • Found one more basic case we should be testing: data-entity-uuid + data-entity-embed-display
  • Realized that we're testing view modes (great) but we're not asserting that the correct view mode is being rendered.
  • And related to view modes: there's weirdness in core with full not existing and then core automatically falling back to default. We should test that too.

Addressed all those points.

Wim Leers’s picture

#124 introduced a much more reliable way of asserting that the embedded entity was rendered.

To resist the temptation to again start asserting based on the body text, I'm removing the body text altogether 🤘 And in doing so, there's one less cache tag associated, which is another nice simplification. (This is because no text format ID was specified and hence it had to resolve the fallback format by reading configuration.)

Wim Leers’s picture

#124 said it'd remove testEntityEmbedDisplayAttributeVersusViewModeAttribute(), but I apparently failed to do so 😅

Done now, and fixed most of the coding standards violations and some nits.

Wim Leers’s picture

+++ b/tests/src/Kernel/EntityEmbedFilterTest.php
@@ -38,12 +38,20 @@ class EntityEmbedFilterTest extends EntityEmbedFilterTestBase {
+    $this->assertRenderedEmbeddedEntityCacheability($result);

This is a remnant from an experiment. Oops.

Wim Leers’s picture

The final bit of clean-up: testInvalidEntity() was not testing anything. But its data provider showed the right intent. OTOH, the assertions would be 99% identical to those in testMissingEntityPlaceholder(). So let's merge them.

Wim Leers’s picture

Issue summary: View changes

Updated issue summary to reflect current status.

One key thing is still missing: recursive embed test coverage. Other than that, I just need to have one last pass through EntityEmbedFilterTest.

Wim Leers’s picture

So: recursive embed test coverage. Let's start.

  1. \Drupal\entity_embed\Exception\RecursiveRenderingException was last touched >3.5 years ago. By a commit that says it's about something else. Without an issue. Apparently that commit moved it from \Drupal\entity_embed\RecursiveRenderingException.
  2. That file in turn was renamed after another commit introduced it but named it incorrectly. (See next bullet.) Again without an issue.
  3. That file in turn was introduced in a commit ~1.5 year prior, that commit said Removed dependency on entity_reference. — because apparently this is not a problem that Entity Embed encountered, it's one that it learned to protect against from the Entity Reference module and its field formatter. Again without issue. The class contained a more useful comment at that time:
     * Exception thrown when the embed entity post_render_cache callback goes into
     * a potentially infinite loop.
    

    … but despite that seemingly pointing to something Entity Embed-specific, it seems to have its roots in the Entity Reference module still.

  4. And it actually goes back to the very first commit of Entity Embed: 48763795 Dave Reid <dave@davereid.net> on 2014-04-18! Clearly, Dave knew about this recursive rendering thing being a problem, probably from his vast experience with Drupal and seemingly having run into complex problems with entity reference field formatters :) The "without an issue" remarks are not meant as snark, but as factual observations that explain why it's hard to figure out the how and why of this and hence how to test this!

So, let's look at the Entity Reference module. It used to contain Drupal\entity_reference\RecursiveRenderingException. Also in Drupal 8 core. Not anymore though. #2404021: entity_reference formatters should be in Core removed it: rather than throwing an exception, it now just logs an error, because that avoids a broken site. And #2073753: Fix and add tests for the recursive rendering protection of the 'Rendered entity' formatter added test coverage for this, more than a year later, and it hasn't been touched since: \Drupal\Tests\field\Kernel\EntityReference\EntityReferenceFormatterTest::testEntityFormatterRecursiveRendering() — yay!

Ironically (and thankfully!) this led me to realize that Entity Embed's recursion protection is currently broken, for the same reasons EntityReferenceEntityFormatter's was broken: see #2073753: Fix and add tests for the recursive rendering protection of the 'Rendered entity' formatter. But that means I'd have to start violating an important principle of this issue: test coverage changes only, no code changes!

So created #3063398: Update EntityEmbedFilter's recursion protection to match EntityReferenceEntityFormatter's and add test coverage for that.

Wim Leers’s picture

Per #130, we're now in the very final stages.

Time to fix the remaining five coding standards violations. Done.

Wim Leers’s picture

Last but not least: some moving around of methods and code blocks for improved clarity. And a few statements that could still be simplified. Now down to zero $this->assertRaw() calls too :)

Wim Leers’s picture

Status: Needs review » Reviewed & tested by the community

Went for a run to think about it once more — I still think this is ready 🥳

  • Wim Leers committed 802b16e on 8.x-1.x
    Issue #3052482 by Wim Leers, oknate, Wannes DR, marcoscano, borisson_:...
Wim Leers’s picture

Status: Reviewed & tested by the community » Fixed
FileSize
1.92 MB

🚢

oknate’s picture

In regards to this:


/**
 * Implements hook_entity_view_alter().
 */
function entity_embed_test_entity_view_alter(&$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
  $build['#attributes']['data-entity-embed-test-view-mode'] = $display->getMode();
}

This has no functional affect, it just allows you to easily test the correct view mode is being used on render? That's cool.

oknate’s picture

I read through the commit. Fantastic stuff. I wish I could have kept up with you on all of it over the last two days. Lots of stuff in there for me for reference on future tests. Thanks for all your hard work on this this week.

Wim Leers’s picture

#136: correct!

#137: Thank you too! You've been instrumental in helping get Entity Embed to where it is today. In particular, without you, we wouldn't have nearly the JavaScript test coverage we do!

oknate’s picture

Thanks. I'm still not clear how much of this module might move into core. Is it just the filter?

  • Wim Leers committed c8a338a on 8.x-1.x
    Issue #3063831 by Wim Leers: Follow-up for #3052482: one incorrect test...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.