function RendererBubblingTest::testConditionalCacheContextBubblingSelfHealing

Same name and namespace in other branches
  1. 9 core/tests/Drupal/Tests/Core/Render/RendererBubblingTest.php \Drupal\Tests\Core\Render\RendererBubblingTest::testConditionalCacheContextBubblingSelfHealing()
  2. 10 core/tests/Drupal/Tests/Core/Render/RendererBubblingTest.php \Drupal\Tests\Core\Render\RendererBubblingTest::testConditionalCacheContextBubblingSelfHealing()
  3. 11.x 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

Class

RendererBubblingTest
@coversDefaultClass <a href="/api/drupal/core%21lib%21Drupal%21Core%21Render%21Renderer.php/class/Renderer/8.9.x" title="Turns a render array into a HTML string." class="local">\Drupal\Core\Render\Renderer</a> @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',
    ]);
}

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.