.../Tests/Core/Render/RendererBubblingTest.php | 137 ++++++++++++++++----- 1 file changed, 104 insertions(+), 33 deletions(-) diff --git a/core/tests/Drupal/Tests/Core/Render/RendererBubblingTest.php b/core/tests/Drupal/Tests/Core/Render/RendererBubblingTest.php index 336a22d..b22ed7e 100644 --- a/core/tests/Drupal/Tests/Core/Render/RendererBubblingTest.php +++ b/core/tests/Drupal/Tests/Core/Render/RendererBubblingTest.php @@ -268,17 +268,18 @@ public function providerTestContextBubblingEdgeCases() { * Tests the self-healing of the redirect with conditional cache contexts. */ public function testConditionalCacheContextBubblingSelfHealing() { - global $foo_cache_context; + global $current_user_role; + $this->setUpRequest(); $this->setupMemoryCache(); $this->cacheContexts->expects($this->any()) ->method('convertTokensToKeys') - ->willReturnCallback(function($context_tokens) use ($foo_cache_context) { - global $foo_cache_context; + ->willReturnCallback(function($context_tokens) { + global $current_user_role; $keys = []; foreach ($context_tokens as $context_id) { - if ($context_id === 'foo') { - $keys[] = $context_id . '.' . ($foo_cache_context ? '1' : '0'); + if ($context_id === 'user.roles') { + $keys[] = 'r.' . $current_user_role; } else { $keys[] = $context_id; @@ -295,94 +296,164 @@ public function testConditionalCacheContextBubblingSelfHealing() { '#markup' => 'parent', 'child' => [ '#cache' => [ - 'contexts' => ['foo'], + 'contexts' => ['user.roles'], 'tags' => ['b'], ], 'grandchild' => [ + '#access_callback' => function () { + global $current_user_role; + // Only role A cannot access this subtree. + return $current_user_role !== 'A'; + }, '#cache' => [ - 'contexts' => ['bar'], + 'contexts' => ['foo'], 'tags' => ['c'], ], + 'grandgrandchild' => [ + '#access_callback' => function () { + global $current_user_role; + // Only role C can access this subtree. + return $current_user_role === 'C'; + }, + '#cache' => [ + 'contexts' => ['bar'], + 'tags' => ['d'], + ], + ], ], ], ]; - // Request 1: grandchild is inaccessible => bubbled cache contexts: foo. + // Request 1: role A, the grandchild isn't accessible => bubbled cache + // contexts: user.roles. $element = $test_element; - $foo_cache_context = FALSE; - $element['child']['grandchild']['#access'] = FALSE; + $current_user_role = 'A'; $this->renderer->render($element); $this->assertRenderCacheItem('parent', [ '#cache_redirect' => TRUE, '#cache' => [ 'keys' => ['parent'], - 'contexts' => ['foo'], + 'contexts' => ['user.roles'], 'tags' => ['a', 'b', 'rendered'], ], ]); - $this->assertRenderCacheItem('parent:foo.0', [ + $this->assertRenderCacheItem('parent:r.A', [ '#attached' => [], '#cache' => [ - 'contexts' => ['foo'], + 'contexts' => ['user.roles'], 'tags' => ['a', 'b', 'rendered'], ], '#post_render_cache' => [], '#markup' => 'parent', ]); - // Request 2: grandchild is accessible => bubbled cache contexts: foo, bar. + // Request 2: role B, the grandchild is accessible => bubbled cache + // contexts: foo, user.roles. $element = $test_element; - $foo_cache_context = TRUE; + $current_user_role = 'B'; $this->renderer->render($element); $this->assertRenderCacheItem('parent', [ '#cache_redirect' => TRUE, '#cache' => [ 'keys' => ['parent'], - 'contexts' => ['bar', 'foo'], + 'contexts' => ['foo', 'user.roles'], 'tags' => ['a', 'b', 'c', 'rendered'], ], ]); - $this->assertRenderCacheItem('parent:bar:foo.1', [ + $this->assertRenderCacheItem('parent:foo:r.B', [ '#attached' => [], '#cache' => [ - 'contexts' => ['bar', 'foo'], + 'contexts' => ['foo', 'user.roles'], 'tags' => ['a', 'b', 'c', 'rendered'], ], '#post_render_cache' => [], '#markup' => 'parent', ]); - // Request 3: grandchild is inaccessible again => bubbled cache contexts: - // foo; 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 'foo' cache context and only the 'bar' cache context, resulting in - // cache miss every time.) + // 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; - $foo_cache_context = FALSE; - $element['child']['grandchild']['#access'] = FALSE; + $current_user_role = 'A'; $this->renderer->render($element); $this->assertRenderCacheItem('parent', [ '#cache_redirect' => TRUE, '#cache' => [ 'keys' => ['parent'], - 'contexts' => ['bar', 'foo'], + 'contexts' => ['foo', 'user.roles'], 'tags' => ['a', 'b', 'c', 'rendered'], ], ]); - $this->assertRenderCacheItem('parent:bar:foo.0', [ + $this->assertRenderCacheItem('parent:foo:r.A', [ '#attached' => [], '#cache' => [ - 'contexts' => ['bar', 'foo'], + 'contexts' => ['foo', 'user.roles'], 'tags' => ['a', 'b', 'rendered'], ], '#post_render_cache' => [], '#markup' => 'parent', ]); + + // Request 4: role C, both the grandchild and the grandgrandchild are + // accessible => bubbled cache contexts: foo, bar, user.roles. + $element = $test_element; + $current_user_role = 'C'; + $this->renderer->render($element); + $final_parent_cache_item = [ + '#cache_redirect' => TRUE, + '#cache' => [ + 'keys' => ['parent'], + 'contexts' => ['bar', 'foo', 'user.roles'], + 'tags' => ['a', 'b', 'c', 'd', 'rendered'], + ], + ]; + $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', 'rendered'], + ], + '#post_render_cache' => [], + '#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->render($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', 'rendered'], + ], + '#post_render_cache' => [], + '#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->render($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', 'rendered'], + ], + '#post_render_cache' => [], + '#markup' => 'parent', + ]); } /**