You are here

public function RendererBubblingTest::testConditionalCacheContextBubblingSelfHealing in Drupal 8

Same name and namespace in other branches
  1. 9 core/tests/Drupal/Tests/Core/Render/RendererBubblingTest.php \Drupal\Tests\Core\Render\RendererBubblingTest::testConditionalCacheContextBubblingSelfHealing()

Tests the self-healing of the redirect with conditional cache contexts.

File

core/tests/Drupal/Tests/Core/Render/RendererBubblingTest.php, line 336
Contains \Drupal\Tests\Core\Render\RendererBubblingTest.

Class

RendererBubblingTest
@coversDefaultClass \Drupal\Core\Render\Renderer @group Render

Namespace

Drupal\Tests\Core\Render

Code

public function testConditionalCacheContextBubblingSelfHealing() {
  $current_user_role =& $this->currentUserRole;
  $this
    ->setUpRequest();
  $this
    ->setupMemoryCache();
  $test_element = [
    '#cache' => [
      'keys' => [
        'parent',
      ],
      'tags' => [
        'a',
      ],
    ],
    '#markup' => 'parent',
    'child' => [
      '#cache' => [
        'contexts' => [
          'user.roles',
        ],
        'tags' => [
          'b',
        ],
      ],
      'grandchild' => [
        '#access_callback' => function () use (&$current_user_role) {

          // Only role A cannot access this subtree.
          return $current_user_role !== 'A';
        },
        '#cache' => [
          'contexts' => [
            'foo',
          ],
          'tags' => [
            'c',
          ],
          // A lower max-age; the redirecting cache item should be updated.
          'max-age' => 1800,
        ],
        'grandgrandchild' => [
          '#access_callback' => function () use (&$current_user_role) {

            // Only role C can access this subtree.
            return $current_user_role === 'C';
          },
          '#cache' => [
            'contexts' => [
              'bar',
            ],
            'tags' => [
              'd',
            ],
            // A lower max-age; the redirecting cache item should be updated.
            'max-age' => 300,
          ],
        ],
      ],
    ],
  ];

  // Request 1: role A, the grandchild isn't accessible => bubbled cache
  // contexts: user.roles.
  $element = $test_element;
  $current_user_role = 'A';
  $this->renderer
    ->renderRoot($element);
  $this
    ->assertRenderCacheItem('parent', [
    '#cache_redirect' => TRUE,
    '#cache' => [
      'keys' => [
        'parent',
      ],
      'contexts' => [
        'user.roles',
      ],
      'tags' => [
        'a',
        'b',
      ],
      'bin' => 'render',
      'max-age' => Cache::PERMANENT,
    ],
  ]);
  $this
    ->assertRenderCacheItem('parent:r.A', [
    '#attached' => [],
    '#cache' => [
      'contexts' => [
        'user.roles',
      ],
      'tags' => [
        'a',
        'b',
      ],
      'max-age' => Cache::PERMANENT,
    ],
    '#markup' => 'parent',
  ]);

  // Request 2: role B, the grandchild is accessible => bubbled cache
  // contexts: foo, user.roles + merged max-age: 1800.
  $element = $test_element;
  $current_user_role = 'B';
  $this->renderer
    ->renderRoot($element);
  $this
    ->assertRenderCacheItem('parent', [
    '#cache_redirect' => TRUE,
    '#cache' => [
      'keys' => [
        'parent',
      ],
      'contexts' => [
        'foo',
        'user.roles',
      ],
      'tags' => [
        'a',
        'b',
        'c',
      ],
      'bin' => 'render',
      'max-age' => 1800,
    ],
  ]);
  $this
    ->assertRenderCacheItem('parent:foo:r.B', [
    '#attached' => [],
    '#cache' => [
      'contexts' => [
        'foo',
        'user.roles',
      ],
      'tags' => [
        'a',
        'b',
        'c',
      ],
      'max-age' => 1800,
    ],
    '#markup' => 'parent',
  ]);

  // Request 3: role A again, the grandchild is inaccessible again => bubbled
  // cache contexts: user.roles; but that's a subset of the already-bubbled
  // cache contexts, so nothing is actually changed in the redirecting cache
  // item. However, the cache item we were looking for in request 1 is
  // technically the same one we're looking for now (it's the exact same
  // request), but with one additional cache context. This is necessary to
  // avoid "cache ping-pong". (Requests 1 and 3 are identical, but without the
  // right merging logic to handle request 2, the redirecting cache item would
  // toggle between only the 'user.roles' cache context and both the 'foo'
  // and 'user.roles' cache contexts, resulting in a cache miss every time.)
  $element = $test_element;
  $current_user_role = 'A';
  $this->renderer
    ->renderRoot($element);
  $this
    ->assertRenderCacheItem('parent', [
    '#cache_redirect' => TRUE,
    '#cache' => [
      'keys' => [
        'parent',
      ],
      'contexts' => [
        'foo',
        'user.roles',
      ],
      'tags' => [
        'a',
        'b',
        'c',
      ],
      'bin' => 'render',
      'max-age' => 1800,
    ],
  ]);
  $this
    ->assertRenderCacheItem('parent:foo:r.A', [
    '#attached' => [],
    '#cache' => [
      'contexts' => [
        'foo',
        'user.roles',
      ],
      'tags' => [
        'a',
        'b',
      ],
      // Note that the max-age here is unaffected. When role A, the grandchild
      // is never rendered, so neither is its max-age of 1800 present here,
      // despite 1800 being the max-age of the redirecting cache item.
      'max-age' => Cache::PERMANENT,
    ],
    '#markup' => 'parent',
  ]);

  // Request 4: role C, both the grandchild and the grandgrandchild are
  // accessible => bubbled cache contexts: foo, bar, user.roles + merged
  // max-age: 300.
  $element = $test_element;
  $current_user_role = 'C';
  $this->renderer
    ->renderRoot($element);
  $final_parent_cache_item = [
    '#cache_redirect' => TRUE,
    '#cache' => [
      'keys' => [
        'parent',
      ],
      'contexts' => [
        'bar',
        'foo',
        'user.roles',
      ],
      'tags' => [
        'a',
        'b',
        'c',
        'd',
      ],
      'bin' => 'render',
      'max-age' => 300,
    ],
  ];
  $this
    ->assertRenderCacheItem('parent', $final_parent_cache_item);
  $this
    ->assertRenderCacheItem('parent:bar:foo:r.C', [
    '#attached' => [],
    '#cache' => [
      'contexts' => [
        'bar',
        'foo',
        'user.roles',
      ],
      'tags' => [
        'a',
        'b',
        'c',
        'd',
      ],
      'max-age' => 300,
    ],
    '#markup' => 'parent',
  ]);

  // Request 5: role A again, verifying the merging like we did for request 3.
  $element = $test_element;
  $current_user_role = 'A';
  $this->renderer
    ->renderRoot($element);
  $this
    ->assertRenderCacheItem('parent', $final_parent_cache_item);
  $this
    ->assertRenderCacheItem('parent:bar:foo:r.A', [
    '#attached' => [],
    '#cache' => [
      'contexts' => [
        'bar',
        'foo',
        'user.roles',
      ],
      'tags' => [
        'a',
        'b',
      ],
      // Note that the max-age here is unaffected. When role A, the grandchild
      // is never rendered, so neither is its max-age of 1800 present here,
      // nor the grandgrandchild's max-age of 300, despite 300 being the
      // max-age of the redirecting cache item.
      'max-age' => Cache::PERMANENT,
    ],
    '#markup' => 'parent',
  ]);

  // Request 6: role B again, verifying the merging like we did for request 3.
  $element = $test_element;
  $current_user_role = 'B';
  $this->renderer
    ->renderRoot($element);
  $this
    ->assertRenderCacheItem('parent', $final_parent_cache_item);
  $this
    ->assertRenderCacheItem('parent:bar:foo:r.B', [
    '#attached' => [],
    '#cache' => [
      'contexts' => [
        'bar',
        'foo',
        'user.roles',
      ],
      'tags' => [
        'a',
        'b',
        'c',
      ],
      // Note that the max-age here is unaffected. When role B, the
      // grandgrandchild is never rendered, so neither is its max-age of 300
      // present here, despite 300 being the max-age of the redirecting cache
      // item.
      'max-age' => 1800,
    ],
    '#markup' => 'parent',
  ]);
}