Loading src/Element/Pattern.php +11 −6 Original line number Diff line number Diff line Loading @@ -173,6 +173,12 @@ class Pattern extends RenderElement implements ContainerFactoryPluginInterface { /** @var \Drupal\patternkit\Entity\PatternInterface $pattern */ $pattern = $element['#pattern']; // Capture base bubbleable metadata. $metadata = BubbleableMetadata::createFromRenderArray($element); // Ensure the metadata from the pattern is present. $metadata->addCacheableDependency($pattern); // Fail early if a pattern was unable to be loaded. if (is_null($pattern)) { return [ Loading @@ -184,14 +190,10 @@ class Pattern extends RenderElement implements ContainerFactoryPluginInterface { $pattern->context = $element['#context']; try { $bubbleableMetadata = new BubbleableMetadata(); $this->fieldProcessorPluginManager->processSchemaValues($pattern, $pattern->config, $pattern->context, $bubbleableMetadata); $this->fieldProcessorPluginManager->processSchemaValues($pattern, $pattern->config, $pattern->context, $metadata); $library_plugin = $this->getPatternLibraryPlugin($pattern); $elements = $library_plugin->render([$pattern]); // Apply all bubbleable metadata from preprocessing. $bubbleableMetadata->applyTo($elements); } catch (SchemaException $exception) { // Replace the pattern output with an error element for more sophisticated Loading @@ -207,6 +209,9 @@ class Pattern extends RenderElement implements ContainerFactoryPluginInterface { ]; } // Ensure all metadata persists to the result. $metadata->applyTo($elements); return $elements; } Loading @@ -225,7 +230,7 @@ class Pattern extends RenderElement implements ContainerFactoryPluginInterface { $pattern_plugin = $pattern->getLibraryPluginId(); $library_plugin_id = !empty($pattern_plugin) ? $pattern_plugin : 'twig'; /** @var \Drupal\patternkit\PatternLibraryPluginInterface */ /** @var \Drupal\patternkit\PatternLibraryPluginInterface $plugin */ $plugin = $this->libraryPluginManager->createInstance($library_plugin_id); return $plugin; } Loading src/Entity/Pattern.php +11 −0 Original line number Diff line number Diff line Loading @@ -403,4 +403,15 @@ class Pattern extends ContentEntityBase implements PatternInterface { return $assets; } /** * {@inheritdoc} */ public function getCacheTags() { if (empty($this->cacheTags)) { $this->cacheTags[] = 'pattern:' . $this->getAssetId(); } return parent::getCacheTags(); } } src/Plugin/Block/PatternkitBlock.php +10 −1 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ use Drupal\Core\Logger\LoggerChannelInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Plugin\Context\ContextHandlerInterface; use Drupal\Core\Plugin\Context\ContextRepositoryInterface; use Drupal\Core\Render\BubbleableMetadata; use Drupal\Core\Template\TwigEnvironment; use Drupal\Core\Utility\Token; use Drupal\patternkit\Entity\PatternInterface; Loading Loading @@ -583,12 +584,20 @@ class PatternkitBlock extends BlockBase implements ContainerFactoryPluginInterfa $config = $this->serializer::decode(reset($data)['value']); } return [ $element = [ '#type' => 'pattern', '#pattern' => $pattern, '#config' => $config, '#context' => $context, ]; // Capture cacheable metadata from dependencies. $metadata = new BubbleableMetadata(); $metadata->addCacheableDependency($pattern); $metadata->addCacheableDependency($patternkit_block); $metadata->applyTo($element); return $element; } /** Loading tests/src/Functional/BlockTest.php 0 → 100644 +130 −0 Original line number Diff line number Diff line <?php namespace Drupal\Tests\patternkit\Functional; use Drupal\Core\Url; use Drupal\node\Entity\Node; /** * Block placement and cache testing for patternkit blocks. * * @group patternkit */ class BlockTest extends PatternkitBrowserTestBase { /** * {@inheritdoc} */ static protected $modules = [ 'patternkit_example', ]; /** * Tests that cache tags are properly set and bubbled up to the page cache. * * Verify that invalidation of these cache tags works: * - "patternkit_pattern:<pattern ID>" * - "patternkit_block:<block plugin ID>" */ public function testBlockCacheTags(): void { // Place an example block in the layout. $pattern_name = '@patternkit/atoms/example/src/example'; $block = $this->createPatternBlock($pattern_name, [ 'text' => 'Pattern block title', 'formatted_text' => 'Pattern block body', 'image_url' => '', 'hidden' => 'Hidden text', ]); $this->placePatternBlockInLayout(Node::load(1), $block); $config = $block->getConfiguration(); $block_id = $config['patternkit_block_id']; /** @var \Drupal\patternkit\Entity\PatternInterface $pattern */ $pattern = $this->patternStorage->loadRevision($config['pattern']); $pattern_id = $pattern->id(); // The page cache only works for anonymous users. $this->drupalLogout(); // Enable page caching. $config = $this->config('system.performance'); $config->set('cache.page.max_age', 300); $config->save(); // Prime the page cache. $this->drupalGet('node/1'); $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'MISS'); // Verify a cache hit, but also the presence of the correct cache tags in // the page cache. $this->drupalGet('node/1'); $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'HIT'); $cid_parts = [ Url::fromUri('entity:node/1', ['absolute' => TRUE]) ->toString(), '', ]; $cid = implode(':', $cid_parts); $cache_entry = \Drupal::cache('page')->get($cid); $expected_cache_tags = [ "pattern:$pattern_name", "patternkit_pattern:$pattern_id", "patternkit_block:$block_id", ]; foreach ($expected_cache_tags as $tag) { $this->assertContains($tag, $cache_entry->tags); } // The pattern entity is modified; verify a cache miss. $pattern->setVersion(\Drupal::VERSION); $pattern->save(); $this->drupalGet('node/1'); $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'MISS'); // Now we should have a cache hit again. $this->drupalGet('node/1'); $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'HIT'); // Place another pattern block; verify a cache miss. $new_pattern_name = '@patternkit/atoms/example_ref/src/example_ref'; $new_block = $this->createPatternBlock($new_pattern_name, [ 'text' => 'Pattern block title', 'nested_reference' => [ 'formatted_text' => 'Pattern block body', 'image_url' => '', 'hidden' => 'Hidden text', ], ]); $this->placePatternBlockInLayout(Node::load(1), $new_block); $config = $block->getConfiguration(); $new_block_id = $config['patternkit_block_id']; /** @var \Drupal\patternkit\Entity\PatternInterface $pattern */ $new_pattern = $this->patternStorage->loadRevision($config['pattern']); $new_pattern_id = $new_pattern->id(); $this->drupalGet('node/1'); $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'MISS'); // Verify a cache hit, but also the presence of the correct cache tags. $this->drupalGet('node/1'); $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'HIT'); $cache_entry = \Drupal::cache('page')->get($cid); $expected_cache_tags = [ "pattern:$pattern_name", "patternkit_pattern:$pattern_id", "patternkit_block:$block_id", "pattern:$new_pattern_name", "patternkit_pattern:$new_pattern_id", "patternkit_block:$new_block_id", ]; foreach ($expected_cache_tags as $tag) { $this->assertContains($tag, $cache_entry->tags); } // Now we should have a cache hit again. $this->drupalGet('node/1'); $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'HIT'); } } tests/src/Kernel/Asset/PatternDiscoveryTest.php +19 −20 Original line number Diff line number Diff line Loading @@ -22,6 +22,20 @@ class PatternDiscoveryTest extends KernelTestBase { 'text', ]; /** * An array containing expected definitions to be discovered. * * @var array|\string[][] */ protected array $expectedDefinitions = [ '@patternkit' => [ '@patternkit/atoms/example/src/example', '@patternkit/atoms/example_filtered/src/example_filtered', '@patternkit/atoms/example_filtered_multiple/src/example_filtered_multiple', '@patternkit/atoms/example_ref/src/example_ref', ], ]; /** * The pattern discovery service being tested. * Loading @@ -42,31 +56,17 @@ class PatternDiscoveryTest extends KernelTestBase { * @covers ::getPatternsByNamespace */ public function testGetPatternsByNamespace() { $expected_patterns = [ '@patternkit/atoms/example/src/example', '@patternkit/atoms/example_filtered/src/example_filtered', '@patternkit/atoms/example_ref/src/example_ref', '@patternkit/atoms/example_filtered_multiple/src/example_filtered_multiple', ]; $definitions = $this->discovery->getPatternsByNamespace('@patternkit'); $this->assertIsArray($definitions); $this->assertCount(count($expected_patterns), $definitions); $this->assertEqualsCanonicalizing($expected_patterns, array_keys($definitions)); $this->assertCount(count($this->expectedDefinitions['@patternkit']), $definitions); $this->assertEqualsCanonicalizing($this->expectedDefinitions['@patternkit'], array_keys($definitions)); } /** * @covers ::getPatternDefinitions */ public function testGetPatternDefinitions() { $expected_patterns = [ '@patternkit/atoms/example/src/example', '@patternkit/atoms/example_filtered/src/example_filtered', '@patternkit/atoms/example_ref/src/example_ref', '@patternkit/atoms/example_filtered_multiple/src/example_filtered_multiple', ]; $definitions = $this->discovery->getPatternDefinitions(); $this->assertIsArray($definitions); Loading @@ -74,9 +74,8 @@ class PatternDiscoveryTest extends KernelTestBase { // With these enabled modules, there should only be one namespace defined. $this->assertEquals(['@patternkit'], array_keys($definitions)); // Within the '@patternkit' namespace there should be a known list of // discovered definitions. $this->assertEqualsCanonicalizing($expected_patterns, array_keys($definitions['@patternkit'])); // Within the @patternkit namespace we should know the definitions found. $this->assertEqualsCanonicalizing($this->expectedDefinitions['@patternkit'], array_keys($definitions['@patternkit'])); } /** Loading @@ -89,7 +88,7 @@ class PatternDiscoveryTest extends KernelTestBase { $cacheEntry = $cache->get($collector::PERSISTENT_CACHE_ID); $this->assertFalse($cacheEntry, 'Expected the pattern discovery cache to be empty initially.'); $definitions = $this->discovery->getPatternDefinitions(); $this->discovery->getPatternDefinitions(); // Trigger end of request destruction to persist data for testing. $collector->destruct(); Loading Loading
src/Element/Pattern.php +11 −6 Original line number Diff line number Diff line Loading @@ -173,6 +173,12 @@ class Pattern extends RenderElement implements ContainerFactoryPluginInterface { /** @var \Drupal\patternkit\Entity\PatternInterface $pattern */ $pattern = $element['#pattern']; // Capture base bubbleable metadata. $metadata = BubbleableMetadata::createFromRenderArray($element); // Ensure the metadata from the pattern is present. $metadata->addCacheableDependency($pattern); // Fail early if a pattern was unable to be loaded. if (is_null($pattern)) { return [ Loading @@ -184,14 +190,10 @@ class Pattern extends RenderElement implements ContainerFactoryPluginInterface { $pattern->context = $element['#context']; try { $bubbleableMetadata = new BubbleableMetadata(); $this->fieldProcessorPluginManager->processSchemaValues($pattern, $pattern->config, $pattern->context, $bubbleableMetadata); $this->fieldProcessorPluginManager->processSchemaValues($pattern, $pattern->config, $pattern->context, $metadata); $library_plugin = $this->getPatternLibraryPlugin($pattern); $elements = $library_plugin->render([$pattern]); // Apply all bubbleable metadata from preprocessing. $bubbleableMetadata->applyTo($elements); } catch (SchemaException $exception) { // Replace the pattern output with an error element for more sophisticated Loading @@ -207,6 +209,9 @@ class Pattern extends RenderElement implements ContainerFactoryPluginInterface { ]; } // Ensure all metadata persists to the result. $metadata->applyTo($elements); return $elements; } Loading @@ -225,7 +230,7 @@ class Pattern extends RenderElement implements ContainerFactoryPluginInterface { $pattern_plugin = $pattern->getLibraryPluginId(); $library_plugin_id = !empty($pattern_plugin) ? $pattern_plugin : 'twig'; /** @var \Drupal\patternkit\PatternLibraryPluginInterface */ /** @var \Drupal\patternkit\PatternLibraryPluginInterface $plugin */ $plugin = $this->libraryPluginManager->createInstance($library_plugin_id); return $plugin; } Loading
src/Entity/Pattern.php +11 −0 Original line number Diff line number Diff line Loading @@ -403,4 +403,15 @@ class Pattern extends ContentEntityBase implements PatternInterface { return $assets; } /** * {@inheritdoc} */ public function getCacheTags() { if (empty($this->cacheTags)) { $this->cacheTags[] = 'pattern:' . $this->getAssetId(); } return parent::getCacheTags(); } }
src/Plugin/Block/PatternkitBlock.php +10 −1 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ use Drupal\Core\Logger\LoggerChannelInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Plugin\Context\ContextHandlerInterface; use Drupal\Core\Plugin\Context\ContextRepositoryInterface; use Drupal\Core\Render\BubbleableMetadata; use Drupal\Core\Template\TwigEnvironment; use Drupal\Core\Utility\Token; use Drupal\patternkit\Entity\PatternInterface; Loading Loading @@ -583,12 +584,20 @@ class PatternkitBlock extends BlockBase implements ContainerFactoryPluginInterfa $config = $this->serializer::decode(reset($data)['value']); } return [ $element = [ '#type' => 'pattern', '#pattern' => $pattern, '#config' => $config, '#context' => $context, ]; // Capture cacheable metadata from dependencies. $metadata = new BubbleableMetadata(); $metadata->addCacheableDependency($pattern); $metadata->addCacheableDependency($patternkit_block); $metadata->applyTo($element); return $element; } /** Loading
tests/src/Functional/BlockTest.php 0 → 100644 +130 −0 Original line number Diff line number Diff line <?php namespace Drupal\Tests\patternkit\Functional; use Drupal\Core\Url; use Drupal\node\Entity\Node; /** * Block placement and cache testing for patternkit blocks. * * @group patternkit */ class BlockTest extends PatternkitBrowserTestBase { /** * {@inheritdoc} */ static protected $modules = [ 'patternkit_example', ]; /** * Tests that cache tags are properly set and bubbled up to the page cache. * * Verify that invalidation of these cache tags works: * - "patternkit_pattern:<pattern ID>" * - "patternkit_block:<block plugin ID>" */ public function testBlockCacheTags(): void { // Place an example block in the layout. $pattern_name = '@patternkit/atoms/example/src/example'; $block = $this->createPatternBlock($pattern_name, [ 'text' => 'Pattern block title', 'formatted_text' => 'Pattern block body', 'image_url' => '', 'hidden' => 'Hidden text', ]); $this->placePatternBlockInLayout(Node::load(1), $block); $config = $block->getConfiguration(); $block_id = $config['patternkit_block_id']; /** @var \Drupal\patternkit\Entity\PatternInterface $pattern */ $pattern = $this->patternStorage->loadRevision($config['pattern']); $pattern_id = $pattern->id(); // The page cache only works for anonymous users. $this->drupalLogout(); // Enable page caching. $config = $this->config('system.performance'); $config->set('cache.page.max_age', 300); $config->save(); // Prime the page cache. $this->drupalGet('node/1'); $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'MISS'); // Verify a cache hit, but also the presence of the correct cache tags in // the page cache. $this->drupalGet('node/1'); $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'HIT'); $cid_parts = [ Url::fromUri('entity:node/1', ['absolute' => TRUE]) ->toString(), '', ]; $cid = implode(':', $cid_parts); $cache_entry = \Drupal::cache('page')->get($cid); $expected_cache_tags = [ "pattern:$pattern_name", "patternkit_pattern:$pattern_id", "patternkit_block:$block_id", ]; foreach ($expected_cache_tags as $tag) { $this->assertContains($tag, $cache_entry->tags); } // The pattern entity is modified; verify a cache miss. $pattern->setVersion(\Drupal::VERSION); $pattern->save(); $this->drupalGet('node/1'); $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'MISS'); // Now we should have a cache hit again. $this->drupalGet('node/1'); $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'HIT'); // Place another pattern block; verify a cache miss. $new_pattern_name = '@patternkit/atoms/example_ref/src/example_ref'; $new_block = $this->createPatternBlock($new_pattern_name, [ 'text' => 'Pattern block title', 'nested_reference' => [ 'formatted_text' => 'Pattern block body', 'image_url' => '', 'hidden' => 'Hidden text', ], ]); $this->placePatternBlockInLayout(Node::load(1), $new_block); $config = $block->getConfiguration(); $new_block_id = $config['patternkit_block_id']; /** @var \Drupal\patternkit\Entity\PatternInterface $pattern */ $new_pattern = $this->patternStorage->loadRevision($config['pattern']); $new_pattern_id = $new_pattern->id(); $this->drupalGet('node/1'); $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'MISS'); // Verify a cache hit, but also the presence of the correct cache tags. $this->drupalGet('node/1'); $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'HIT'); $cache_entry = \Drupal::cache('page')->get($cid); $expected_cache_tags = [ "pattern:$pattern_name", "patternkit_pattern:$pattern_id", "patternkit_block:$block_id", "pattern:$new_pattern_name", "patternkit_pattern:$new_pattern_id", "patternkit_block:$new_block_id", ]; foreach ($expected_cache_tags as $tag) { $this->assertContains($tag, $cache_entry->tags); } // Now we should have a cache hit again. $this->drupalGet('node/1'); $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'HIT'); } }
tests/src/Kernel/Asset/PatternDiscoveryTest.php +19 −20 Original line number Diff line number Diff line Loading @@ -22,6 +22,20 @@ class PatternDiscoveryTest extends KernelTestBase { 'text', ]; /** * An array containing expected definitions to be discovered. * * @var array|\string[][] */ protected array $expectedDefinitions = [ '@patternkit' => [ '@patternkit/atoms/example/src/example', '@patternkit/atoms/example_filtered/src/example_filtered', '@patternkit/atoms/example_filtered_multiple/src/example_filtered_multiple', '@patternkit/atoms/example_ref/src/example_ref', ], ]; /** * The pattern discovery service being tested. * Loading @@ -42,31 +56,17 @@ class PatternDiscoveryTest extends KernelTestBase { * @covers ::getPatternsByNamespace */ public function testGetPatternsByNamespace() { $expected_patterns = [ '@patternkit/atoms/example/src/example', '@patternkit/atoms/example_filtered/src/example_filtered', '@patternkit/atoms/example_ref/src/example_ref', '@patternkit/atoms/example_filtered_multiple/src/example_filtered_multiple', ]; $definitions = $this->discovery->getPatternsByNamespace('@patternkit'); $this->assertIsArray($definitions); $this->assertCount(count($expected_patterns), $definitions); $this->assertEqualsCanonicalizing($expected_patterns, array_keys($definitions)); $this->assertCount(count($this->expectedDefinitions['@patternkit']), $definitions); $this->assertEqualsCanonicalizing($this->expectedDefinitions['@patternkit'], array_keys($definitions)); } /** * @covers ::getPatternDefinitions */ public function testGetPatternDefinitions() { $expected_patterns = [ '@patternkit/atoms/example/src/example', '@patternkit/atoms/example_filtered/src/example_filtered', '@patternkit/atoms/example_ref/src/example_ref', '@patternkit/atoms/example_filtered_multiple/src/example_filtered_multiple', ]; $definitions = $this->discovery->getPatternDefinitions(); $this->assertIsArray($definitions); Loading @@ -74,9 +74,8 @@ class PatternDiscoveryTest extends KernelTestBase { // With these enabled modules, there should only be one namespace defined. $this->assertEquals(['@patternkit'], array_keys($definitions)); // Within the '@patternkit' namespace there should be a known list of // discovered definitions. $this->assertEqualsCanonicalizing($expected_patterns, array_keys($definitions['@patternkit'])); // Within the @patternkit namespace we should know the definitions found. $this->assertEqualsCanonicalizing($this->expectedDefinitions['@patternkit'], array_keys($definitions['@patternkit'])); } /** Loading @@ -89,7 +88,7 @@ class PatternDiscoveryTest extends KernelTestBase { $cacheEntry = $cache->get($collector::PERSISTENT_CACHE_ID); $this->assertFalse($cacheEntry, 'Expected the pattern discovery cache to be empty initially.'); $definitions = $this->discovery->getPatternDefinitions(); $this->discovery->getPatternDefinitions(); // Trigger end of request destruction to persist data for testing. $collector->destruct(); Loading