Skip to content

Commit

Permalink
Move property mock logic to Twig compiler (#42)
Browse files Browse the repository at this point in the history
  • Loading branch information
squrious authored Dec 10, 2024
1 parent a4a2385 commit 8c0ca89
Show file tree
Hide file tree
Showing 25 changed files with 329 additions and 168 deletions.
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@
"symfony/css-selector": "^6.4|^7.0",
"symfony/framework-bundle": "^6.4|^7.0",
"symfony/phpunit-bridge": "^6.4|^7.0",
"symfony/stimulus-bundle": "^2.16",
"symfony/ux-live-component": "^2.16",
"symfony/ux-twig-component": "2.16",
"symfony/stimulus-bundle": "^2.21",
"symfony/ux-live-component": "^2.21",
"symfony/ux-twig-component": "^2.21",
"symfonycasts/sass-bundle": "^0.5.1",
"symfonycasts/tailwind-bundle": "^0.5.0"
},
Expand Down
2 changes: 1 addition & 1 deletion sandbox/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"symfony/stimulus-bundle": "^2.17",
"symfony/twig-bundle": "7.0.*",
"symfony/ux-live-component": "^2.17",
"symfony/ux-twig-component": "2.16",
"symfony/ux-twig-component": "^2.16",
"symfony/yaml": "7.0.*",
"twig/extra-bundle": "^2.12|^3.0",
"twig/twig": "^2.12|^3.0"
Expand Down
8 changes: 8 additions & 0 deletions sandbox/templates/components/ProductTable.stories.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import ProductTable from './ProductTable.html.twig';
import {twig} from "@sensiolabs/storybook-symfony-webpack5";

export default {
component: ProductTable,
};

export const Default = {
}

export const EmbeddedRender = {
render: () => ({
components: { ProductTable },
template: twig`<twig:ProductTable></twig:ProductTable>`,
}),
}
36 changes: 28 additions & 8 deletions src/DependencyInjection/StorybookExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,26 @@
use Storybook\Command\StorybookInitCommand;
use Storybook\Controller\StorybookController;
use Storybook\DependencyInjection\Compiler\ComponentMockPass;
use Storybook\EventListener\ComponentMockSubscriber;
use Storybook\EventListener\ProxyRequestListener;
use Storybook\Exception\UnauthorizedStoryException;
use Storybook\Mock\ComponentProxyFactory;
use Storybook\StoryRenderer;
use Storybook\Twig\StorybookEnvironment;
use Storybook\Twig\StorybookEnvironmentConfigurator;
use Storybook\Twig\StoryExtension;
use Storybook\Twig\TwigComponentSubscriber;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\Processor;
use Symfony\Component\DependencyInjection\Argument\AbstractArgument;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\DependencyInjection\Reference;
use Twig\Extension\SandboxExtension;
use Twig\Sandbox\SecurityPolicy;

/**
Expand Down Expand Up @@ -97,14 +100,35 @@ static function (ChildDefinition $definition, AsComponentMock $attributeInstance
->setArgument(4, $sandboxConfig['allowedFunctions'])
;

// Storybook Twig extensions
$container->setDefinition('storybook.twig', new ChildDefinition('twig'))
->setClass(StorybookEnvironment::class)
->addMethodCall('setComponentRuntime', [new Reference('storybook.twig.component_runtime')])
->setConfigurator([new Reference('storybook.twig.environment_configurator'), 'configure'])
;

$container->register('storybook.twig.extension.sandbox', SandboxExtension::class)
->setArgument(0, new Reference('storybook.twig.security_policy'))
->addTag('storybook.twig.extension')
;

$container->register('storybook.twig.extension.story', StoryExtension::class)
->setArgument(0, new Reference('storybook.component_proxy_factory'))
->addTag('storybook.twig.extension')
;

$container->register('storybook.twig.environment_configurator', StorybookEnvironmentConfigurator::class)
->setArgument(0, new Reference('twig.configurator.environment'))
->setArgument(1, new Reference('storybook.twig.security_policy'))
->setArgument(1, new TaggedIteratorArgument('storybook.twig.extension'))
->setArgument(2, $config['cache'] ?? false)
;

$container->setDefinition('storybook.twig', new ChildDefinition('twig'))
->setConfigurator([new Reference('storybook.twig.environment_configurator'), 'configure'])
$container->setDefinition('storybook.twig.component_runtime', new ChildDefinition('.ux.twig_component.twig.component_runtime'))
->replaceArgument(0, new Reference('storybook.twig.component_renderer'))
;

$container->setDefinition('storybook.twig.component_renderer', new ChildDefinition('ux.twig_component.component_renderer'))
->replaceArgument(0, new Reference('storybook.twig'))
;

$container->register('storybook.story_renderer', StoryRenderer::class)
Expand Down Expand Up @@ -135,10 +159,6 @@ static function (ChildDefinition $definition, AsComponentMock $attributeInstance
->setArgument(0, new Reference('request_stack'))
->setArgument(1, new Reference('event_dispatcher'))
->addTag('kernel.event_subscriber');

$container->register('storybook.component_mock_subscriber', ComponentMockSubscriber::class)
->setArgument(0, new Reference('storybook.component_proxy_factory'))
->addTag('kernel.event_subscriber');
}

public function getConfigTreeBuilder(): TreeBuilder
Expand Down
45 changes: 0 additions & 45 deletions src/EventListener/ComponentMockSubscriber.php

This file was deleted.

7 changes: 5 additions & 2 deletions src/Mock/MockedPropertiesProxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@
*/
final class MockedPropertiesProxy
{
public function __construct(private readonly object $component, private readonly object $provider, private readonly array $mockedMethods)
{
public function __construct(
private readonly object $component,
private readonly object $provider,
private readonly array $mockedMethods,
) {
}

public function __call(string $name, array $args)
Expand Down
7 changes: 2 additions & 5 deletions src/StoryRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,10 @@ public function render(Story $story): string
{
$storyTemplateName = \sprintf('story_%s.html.twig', $story->getId());

// Name included template with a hash to avoid reusing an already loaded template class
$templateName = \sprintf('%s.html.twig', hash('xxh128', $story->getTemplate()));

$loader = new ChainLoader([
new ArrayLoader([
$templateName => $story->getTemplate(),
$storyTemplateName => \sprintf("{%% sandbox %%} {%%- include '%s' -%%} {%% endsandbox %%}", $templateName),
$story->getTemplateName() => $story->getTemplate(),
$storyTemplateName => \sprintf("{%% sandbox %%} {%%- include '%s' -%%} {%% endsandbox %%}", $story->getTemplateName()),
]),
$originalLoader = $this->twig->getLoader(),
]);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<?php

namespace Storybook\Twig\Sandbox\Node;
namespace Storybook\Twig\Node;

use Twig\Attribute\YieldReady;
use Twig\Compiler;
use Twig\Node\Node;

Expand All @@ -12,7 +13,7 @@
*
* @internal
*/
// #[YieldReady]
#[YieldReady]
class BypassCheckSecurityCallNode extends Node
{
public function compile(Compiler $compiler): void
Expand Down
38 changes: 38 additions & 0 deletions src/Twig/Node/MockContextNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace Storybook\Twig\Node;

use Storybook\Twig\StoryExtension;
use Twig\Attribute\YieldReady;
use Twig\Compiler;
use Twig\Node\Node;

/**
* Mock context array with provided Storybook context helper if available.
*
* @author Nicolas Rigaud <[email protected]>
*
* @internal
*/
#[YieldReady]
final class MockContextNode extends Node
{
public function compile(Compiler $compiler): void
{
$storybookExtension = $compiler->getVarName();

$compiler
->write('if (isset($context["__storybook"])) {')
->raw("\n")
->indent()
->write(\sprintf('$%s = $this->env->getExtension(', $storybookExtension))
->string(StoryExtension::class)
->raw(");\n")
->write(\sprintf('$%s->prepareContext($context);', $storybookExtension))
->raw("\n")
->outdent()
->write('}')
->raw("\n\n")
;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<?php

namespace Storybook\Twig\Sandbox\Node;
namespace Storybook\Twig\Node;

use Twig\Attribute\YieldReady;
use Twig\Compiler;
use Twig\Node\BodyNode;

Expand All @@ -12,12 +13,12 @@
*
* @internal
*/
// #[YieldReady]
#[YieldReady]
class SafeBodyNode extends BodyNode
{
public function __construct(BodyNode $body)
{
parent::__construct($body->nodes, $body->attributes, $body->lineno, $body->tag);
parent::__construct($body->nodes, $body->attributes, $body->lineno);
}

public function compile(Compiler $compiler): void
Expand Down
38 changes: 38 additions & 0 deletions src/Twig/NodeVisitor/MockContextNodeVisitor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace Storybook\Twig\NodeVisitor;

use Storybook\Twig\Node\MockContextNode;
use Twig\Environment;
use Twig\Node\ModuleNode;
use Twig\Node\Node;
use Twig\NodeVisitor\NodeVisitorInterface;

/**
* Node visitor to inject mock context logic before displaying a module node.
*
* @author Nicolas Rigaud <[email protected]>
*
* @internal
*/
final class MockContextNodeVisitor implements NodeVisitorInterface
{
public function enterNode(Node $node, Environment $env): Node
{
return $node;
}

public function leaveNode(Node $node, Environment $env): ?Node
{
if ($node instanceof ModuleNode && '' !== $node->getSourceContext()->getPath()) {
$node->setNode('display_start', new Node([new MockContextNode(), $node->getNode('display_start')]));
}

return $node;
}

public function getPriority(): int
{
return 0;
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<?php

namespace Storybook\Twig\Sandbox;
namespace Storybook\Twig\NodeVisitor;

use Storybook\Twig\Sandbox\Node\BypassCheckSecurityCallNode;
use Storybook\Twig\Sandbox\Node\SafeBodyNode;
use Storybook\Twig\Node\BypassCheckSecurityCallNode;
use Storybook\Twig\Node\SafeBodyNode;
use Twig\Environment;
use Twig\Node\CheckSecurityCallNode;
use Twig\Node\CheckSecurityNode;
Expand Down
50 changes: 48 additions & 2 deletions src/Twig/StoryExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

namespace Storybook\Twig;

use Storybook\Twig\Sandbox\SandboxNodeVisitor;
use Storybook\Mock\ComponentProxyFactory;
use Storybook\Mock\MockedPropertiesProxy;
use Storybook\Twig\NodeVisitor\MockContextNodeVisitor;
use Storybook\Twig\NodeVisitor\SandboxNodeVisitor;
use Storybook\Util\StorybookContextHelper;
use Twig\Extension\AbstractExtension;

/**
Expand All @@ -12,8 +16,50 @@
*/
final class StoryExtension extends AbstractExtension
{
public function __construct(
private readonly ComponentProxyFactory $componentProxyFactory,
) {
}

public function getNodeVisitors(): array
{
return [new SandboxNodeVisitor()];
return [
new MockContextNodeVisitor(),
new SandboxNodeVisitor(),
];
}

public function prepareContext(array &$context): void
{
if (false === StorybookContextHelper::hasStorybookContext($context)) {
return;
}

$storybookContext = StorybookContextHelper::getStorybookContext($context);

if (null === ($componentClass = $storybookContext->componentClass)) {
// Don't mock anonymous components
return;
}

if (false === $this->componentProxyFactory->componentHasMock($componentClass)) {
return;
}

if (false === ($context['this'] instanceof MockedPropertiesProxy)) {
$context['this'] = $this->componentProxyFactory->createProxyForStory(
$componentClass,
$context['this'],
$storybookContext->story,
);
}

if (false === ($context['computed'] instanceof MockedPropertiesProxy)) {
$context['computed'] = $this->componentProxyFactory->createProxyForStory(
$componentClass,
$context['computed'],
$storybookContext->story
);
}
}
}
Loading

0 comments on commit 8c0ca89

Please sign in to comment.