diff --git a/README.md b/README.md index a0747bf..7839355 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@
-

AMP Client PHP

+AMP Client PHP Logo +

AMP Client PHP

:mega: PHP Client for Advertising Management Platform

@@ -19,10 +20,9 @@ $ composer require 68publishers/amp-client ## Versions compatibility matrix -| PHP client version | PHP version | AMP version | API version | -|:----------------------:|-------------|:----------------:|:-----------:| -| `^1.0` | `>=7.4` | `>=2.12` | `1` | - +| PHP client version | PHP version | AMP version | API version | +|:------------------:|-------------|:----------------:|:-----------:| +| `>=1.0` | `>=7.4` | `>=2.12` | `1` | ## Integration without a framework diff --git a/docs/images/logo.png b/docs/images/logo.png new file mode 100644 index 0000000..38b29ee Binary files /dev/null and b/docs/images/logo.png differ diff --git a/docs/integration-with-nette-framework.md b/docs/integration-with-nette-framework.md index 938c770..eb86834 100644 --- a/docs/integration-with-nette-framework.md +++ b/docs/integration-with-nette-framework.md @@ -1,9 +1,14 @@ -# Integration with Nette framework +
+AMP Client PHP Logo +

AMP Client PHP

+

Integration with Nette framework

+
For more information on how the client works, we also recommend reading the [Integration without a framework](./integration-without-framework.md) section. * [Client integration](#client-integration) * [Latte macros integration](#latte-macros-integration) + * [Using multiple rendering modes](#using-multiple-rendering-modes) * [Configuring client before the first fetch](#configuring-client-before-the-first-fetch) * [Renaming the macro](#renaming-the-macro) @@ -64,6 +69,7 @@ amp_client: random: %appDir%/templates/amp/random.latte multiple: %appDir%/templates/amp/multiple.latte not_found: %appDir%/templates/amp/not_found.latte + client_side: %appDir%/templates/amp/client_side.latte ``` Two important services are now available in the DI Container - `AmpClientInterface` and `RendererInterface`. @@ -100,23 +106,24 @@ final class MyPresenter extends Presenter { ## Latte macros integration -Banners can be rendered directly from the Latte template without having to manually call the client. We need to register another extension for this: +Banners can be rendered directly from the Latte template without having to manually call the client. Another extension must be registered for this: ```neon extensions: amp_client.latte: SixtyEightPublishers\AmpClient\Bridge\Nette\DI\AmpClientLatteExtension(%debugMode%) ``` -Now we have the macro `{banner}` available in the application, and we can use it in templates: +Now the macro `{banner}` is available in the application and can be used in templates: ```latte {banner homepage.top} {banner homepage.promo, resources: ['role' => 'guest']} +{banner homepage.bottom, attributes: ['class' => 'my-awesome-class']} ``` Banners are now requested via API and rendered to the template automatically. -Each `{banner}` macro makes a separate request to the AMP API, so in our example above, two requests are sent. +Each `{banner}` macro makes a separate request to the AMP API, so in our example above, three requests are sent. This can be solved by the following configuration: ```neon @@ -127,6 +134,32 @@ amp_client.latte: Now when rendering a page via `nette/application`, information about all banners to be rendered is collected and a request to the AMP API is sent only once the whole template is rendered. The banners are then inserted back into the rendered page. This behavior also works automatically with AJAX snippets. +The following rendering modes are available: + +- **direct** ([DirectRenderingMode](../src/Bridge/Latte/RenderingMode/DirectRenderingMode.php)) - The default mode, API is requested separately for each banner. +- **client_side** ([ClientSideRenderingMode](../src/Bridge/Latte/RenderingMode/ClientSideRenderingMode.php)) - Renders only a wrapper element and leaves loading banners on the JavaScript client. Banners are loaded by calling the `attachBanners()` function. +- **queued_in_presenter_context** ([QueuedRenderingInPresenterContextMode](../src/Bridge/Latte/RenderingMode/QueuedRenderingInPresenterContextMode.php)) - Renders only HTML comments as placeholders and stores requested positions in a queue. It will request, render and place all banners to them positions at once before the presenter returns a response. +- **queued** ([QueuedRenderingMode](../src/Bridge/Latte/RenderingMode/QueuedRenderingMode.php)) - Same behavior as `queued_in_presenter_context`, but it doesn't take into account whether the website template is currently being rendered through the Nette application. It is more suited for an integration without a framework. + +### Using multiple rendering modes + +Besides the default rendering mode, which is set by the option `rendering_mode`, it is possible to configure alternative modes that can be used in templates. + +```neon +amp_client.latte: + rendering_mode: queued_in_presenter_context # the default value is "direct" + alternative_rendering_modes: + - client_side +``` + +```latte +{* The first banner will be rendered with the default mode *} +{banner homepage.top} + +{* The second banner will be rendered client side *} +{banner homepage.promo, mode: 'client_side'} +``` + ### Configuring client before the first fetch Occasionally, we may want to configure the client before making a request to the AMP API from the template. diff --git a/docs/integration-without-framework.md b/docs/integration-without-framework.md index 6432e67..26d7915 100644 --- a/docs/integration-without-framework.md +++ b/docs/integration-without-framework.md @@ -1,11 +1,20 @@ -# Integration without a framework +
+AMP Client PHP Logo +

AMP Client PHP

+

Integration without a framework

+
* [Client initialization](#client-initialization) * [Cache](#cache) * [Custom Guzzle options](#custom-guzzle-options) * [Fetching banners](#fetching-banners) * [Rendering banners](#rendering-banners) + * [Rendering banners on the client side](#rendering-banners-on-the-client-side) + * [Templates overwriting](#templates-overwriting) + * [Rendering banners using Latte](#rendering-banners-using-latte) * [Latte templating system integration](#latte-templating-system-integration) + * [Using multiple rendering modes](#using-multiple-rendering-modes) + * [Renaming the macro](#renaming-the-macro) ## Client initialization @@ -143,7 +152,6 @@ $homepagePromo = $response->getPosition('homepage.promo'); Banners can be rendered simply by using the `Renderer` class: ```php -use SixtyEightPublishers\AmpClient\AmpClientInterface; use SixtyEightPublishers\AmpClient\Renderer\Renderer; use SixtyEightPublishers\AmpClient\Response\BannersResponse; @@ -154,6 +162,30 @@ $renderer = Renderer::create(); echo $renderer->render($response->getPosition('homepage.top')); ``` +The second argument can be used to pass an array of attributes to be contained in the banner's HTML wrapper element. + +```php +echo $renderer->render($response->getPosition('homepage.top', ['class' => 'my-awesome-class'])); +``` + +### Rendering banners on the client side + +Banner rendering can be left to the [JavaScript client](https://github.com/68publishers/amp-client-js) using the `Renderer::renderClientSide()` method. + +```php +use SixtyEightPublishers\AmpClient\Renderer\Renderer; +use SixtyEightPublishers\AmpClient\Request\ValueObject\Position; + +$renderer = Renderer::create(); + +echo $renderer->renderClientSide(new Position('homepage.top')); +echo $renderer->renderClientSide(new Position('homepage.promo'), ['class' => 'my-awesome-class']); +``` + +Banners rendered in this way will be loaded by the JavaScript client when its `attachBanners()` function is called. + +### Templates overwriting + The default templates are written as `.phtml` templates and can be found [here](../src/Renderer/Phtml/Templates). Templates can be also overwritten: ```php @@ -163,7 +195,7 @@ use SixtyEightPublishers\AmpClient\Renderer\Templates; $bridge = new PhtmlRendererBridge(); $bridge = $bridge->overrideTemplates(new Templates([ - Templates::TemplateSingle => '/my_custom_template_for_single_position.phtml', + Templates::Single => '/my_custom_template_for_single_position.phtml', ])); $renderer = Renderer::create($bridge); @@ -175,10 +207,11 @@ The following template types can be overwritten: use SixtyEightPublishers\AmpClient\Renderer\Templates; new Templates([ - Templates::TemplateSingle => '/single.phtml', # for positions with the display type "single" - Templates::TemplateMultiple => '/multiple.phtml', # for positions with the display type "multiple" - Templates::TemplateRandom => '/random.phtml', # for positions with the display type "random" - Templates::TemplateNotFound => '/notFound.phtml', # for positions that were not found + Templates::Single => '/single.phtml', # for positions with the display type "single" + Templates::Multiple => '/multiple.phtml', # for positions with the display type "multiple" + Templates::Random => '/random.phtml', # for positions with the display type "random" + Templates::NotFound => '/notFound.phtml', # for positions that were not found + Templates::ClientSide => '/clientSide.phtml', ]) ``` @@ -245,12 +278,13 @@ $engine->render(__DIR__ . '/template.latte'); {banner homepage.top} {banner homepage.promo, resources: ['role' => 'guest']} +{banner homepage.bottom, attributes: ['class' => 'my-awesome-class']} ``` Banners are now requested via API and rendered to the template automatically. -Each `{banner}` macro makes a separate request to the AMP API, so in our example above, two requests are sent. -This can be solved, however you need to render the Latte to a text string, not a buffer. +By default, each `{banner}` macro makes a separate request to the AMP API, so in our example above, three requests are sent. +This can be solved, however you need to render the Latte to a string, not a buffer. ```php use SixtyEightPublishers\AmpClient\AmpClientInterface; @@ -265,7 +299,7 @@ use Latte\Engine; $engine = new Engine(); $provider = (new RendererProvider($client,$renderer)) - ->setDebugMode(true) # exceptions from Client and Renderer are suppressed in non-debug mode + ->setDebugMode(true) ->setRenderingMode(new QueuedRenderingMode()); AmpClientLatteExtension::register($engine, $provider); @@ -276,3 +310,55 @@ echo $provider->renderQueuedPositions($output); ``` Now the client requests both banners in the template with one request. + +The following rendering modes are available: + +- **direct** ([DirectRenderingMode](../src/Bridge/Latte/RenderingMode/DirectRenderingMode.php)) - The default mode, API is requested separately for each banner. +- **client_side** ([ClientSideRenderingMode](../src/Bridge/Latte/RenderingMode/ClientSideRenderingMode.php)) - Renders only a wrapper element and leaves loading banners on the JavaScript client. Banners are loaded by calling the `attachBanners()` function. +- **queued** ([QueuedRenderingMode](../src/Bridge/Latte/RenderingMode/QueuedRenderingMode.php)) - Renders only HTML comments as placeholders and stores requested positions in a queue. It will request and render all banners at once when the `RendererProvider::renderQueuedPositions()` method is called. +- **queued_in_presenter_context** ([QueuedRenderingInPresenterContextMode](../src/Bridge/Latte/RenderingMode/QueuedRenderingInPresenterContextMode.php)) - Same behavior as `queued` but in the context of a Presenter only. Usable with Nette applications only. + +### Using multiple rendering modes + +Besides the default rendering mode, which is set by the method `RendererProvider::setRenderingMode()`, it is possible to configure alternative modes that can be used in templates. + +```php +use SixtyEightPublishers\AmpClient\Bridge\Latte\RendererProvider; +use SixtyEightPublishers\AmpClient\Bridge\Latte\RenderingMode\DirectRenderingMode; +use SixtyEightPublishers\AmpClient\Bridge\Latte\RenderingMode\ClientSideRenderingMode; + +/** @var AmpClientInterface $client */ +/** @var RendererInterface $renderer */ + +$provider = new RendererProvider($client, $renderer); + +$provider->setRenderingMode(new DirectRenderingMode()); # No need to actually set it up, this mode is the default. +$provider->setAlternativeRenderingModes([ + new ClientSideRenderingMode(), +]); +``` + +```latte +{* The first banner will be rendered with the default mode (directly) *} +{banner homepage.top} + +{* The second banner will be rendered client side *} +{banner homepage.promo, mode: 'client_side'} +``` + +### Renaming the macro + +Macro `{banner}` can be renamed. This can be done by specifying the third argument of the method `AmpClientLatteExtension::register()`. + +```php +use SixtyEightPublishers\AmpClient\Bridge\Latte\AmpClientLatteExtension; +use SixtyEightPublishers\AmpClient\Bridge\Latte\RendererProvider; +use Latte\Engine; + +/** @var RendererProvider $provider */ +/** @var Engine $engine */ + +AmpClientLatteExtension::register($engine, $provider, 'ampBanner'); +``` + +The macro will now be named `{ampBanner}`. diff --git a/src/Bridge/Latte/RendererProvider.php b/src/Bridge/Latte/RendererProvider.php index e2789d7..901b638 100644 --- a/src/Bridge/Latte/RendererProvider.php +++ b/src/Bridge/Latte/RendererProvider.php @@ -4,6 +4,7 @@ namespace SixtyEightPublishers\AmpClient\Bridge\Latte; +use InvalidArgumentException; use Psr\Log\LoggerInterface; use SixtyEightPublishers\AmpClient\AmpClientInterface; use SixtyEightPublishers\AmpClient\Bridge\Latte\Event\ConfigureClientEvent; @@ -23,14 +24,19 @@ use function array_values; use function assert; use function count; +use function gettype; use function htmlspecialchars; use function is_array; +use function is_scalar; +use function is_string; use function sprintf; use function str_replace; final class RendererProvider { private const OptionResources = 'resources'; + private const OptionAttributes = 'attributes'; + private const OptionMode = 'mode'; private AmpClientInterface $client; @@ -40,9 +46,16 @@ final class RendererProvider private RenderingModeInterface $renderingMode; + /** @var array */ + private array $alternativeRenderingModes = []; + private bool $debugMode = false; - /** @var array}> */ + /** @var array, + * }> + */ private array $queue = []; /** @var array> */ @@ -70,13 +83,24 @@ public function __construct( */ public function __invoke(object $globals, string $positionCode, array $options = []): string { + $renderingMode = $this->resolveRenderingMode($options); $position = $this->createPosition($positionCode, $options); - if ($this->renderingMode->shouldBePositionQueued($position, $globals)) { + if ($renderingMode->shouldBePositionRenderedClientSide($position)) { + return $this->renderClientSidePosition($position, $options); + } + + if ($renderingMode->shouldBePositionQueued($position, $globals)) { return $this->addToQueue($position, $options); - } else { - return $this->render($position, $options); } + + $response = $this->fetchResponse(new BannersRequest([$position])); + + if (null === $response || null === $response->getPosition($positionCode)) { + return ''; + } + + return $this->renderPosition($response->getPosition($positionCode), $options); } public function setDebugMode(bool $debugMode): self @@ -93,32 +117,31 @@ public function setRenderingMode(RenderingModeInterface $renderingMode): self return $this; } - public function addConfigureClientEventHandler(ConfigureClientEventHandlerInterface $handler): self + /** + * @param array $renderingModes + */ + public function setAlternativeRenderingModes(array $renderingModes): self { - $this->eventHandlers[ConfigureClientEventHandlerInterface::class][] = $handler; + $this->alternativeRenderingModes = []; + + foreach ($renderingModes as $renderingMode) { + assert($renderingMode instanceof RenderingModeInterface); + + $this->alternativeRenderingModes[$renderingMode->getName()] = $renderingMode; + } return $this; } - /** - * @param array $options - * - * @throws AmpExceptionInterface - */ - private function render(RequestPosition $position, array $options): string + public function addConfigureClientEventHandler(ConfigureClientEventHandlerInterface $handler): self { - $positionCode = $position->getCode(); - $response = $this->fetchResponse(new BannersRequest([$position])); - - if (null === $response || null === $response->getPosition($positionCode)) { - return ''; - } + $this->eventHandlers[ConfigureClientEventHandlerInterface::class][] = $handler; - return $this->renderPosition($response->getPosition($positionCode), $options); + return $this; } /** - * @param array $options * + * @param array $options */ private function addToQueue(RequestPosition $position, array $options): string { @@ -130,7 +153,17 @@ private function addToQueue(RequestPosition $position, array $options): string public function supportsQueues(): bool { - return $this->renderingMode->supportsQueues(); + if ($this->renderingMode->supportsQueues()) { + return true; + } + + foreach ($this->alternativeRenderingModes as $alternativeRenderingMode) { + if ($alternativeRenderingMode->supportsQueues()) { + return true; + } + } + + return false; } public function isAnythingQueued(): bool @@ -245,7 +278,7 @@ private function fetchResponse(BannersRequest $request): ?BannersResponse private function renderPosition(ResponsePosition $position, array $options): string { try { - $elementAttributes = (array) ($options['attributes'] ?? []); + $elementAttributes = (array) ($options[self::OptionAttributes] ?? []); return $this->renderer->render($position, $elementAttributes); } catch (RendererException $e) { @@ -263,6 +296,30 @@ private function renderPosition(ResponsePosition $position, array $options): str } } + /** + * @param array $options + */ + private function renderClientSidePosition(RequestPosition $position, array $options): string + { + try { + $elementAttributes = (array) ($options[self::OptionAttributes] ?? []); + + return $this->renderer->renderClientSide($position, $elementAttributes); + } catch (RendererException $e) { + if ($this->debugMode) { + throw $e; + } + + if (null !== $this->logger) { + $this->logger->error($e->getMessage(), [ + 'exception' => $e, + ]); + } + + return ''; + } + } + /** * @param array $options */ @@ -278,6 +335,36 @@ private function createPosition(string $positionCode, array $options): RequestPo return new RequestPosition($positionCode, $bannerResources); } + /** + * @param array $options + */ + private function resolveRenderingMode(array $options): RenderingModeInterface + { + if (!isset($options[self::OptionMode])) { + return $this->renderingMode; + } + + $mode = $options[self::OptionMode]; + + if ($mode instanceof RenderingModeInterface) { + $mode = $mode->getName(); + } + + if ($this->renderingMode->getName() === $mode) { + return $this->renderingMode; + } + + if (!is_string($mode) || !isset($this->alternativeRenderingModes[$mode])) { + throw new InvalidArgumentException(sprintf( + 'Invalid value for option "%s". The value %s is not registered between alternative rendering modes.', + self::OptionMode, + is_scalar($mode) ? "\"$mode\"" : gettype($mode), + )); + } + + return $this->alternativeRenderingModes[$mode]; + } + private function formatHtmlComment(string $positionCode): string { return sprintf( diff --git a/src/Bridge/Latte/RenderingMode/ClientSideRenderingMode.php b/src/Bridge/Latte/RenderingMode/ClientSideRenderingMode.php new file mode 100644 index 0000000..51eadfe --- /dev/null +++ b/src/Bridge/Latte/RenderingMode/ClientSideRenderingMode.php @@ -0,0 +1,32 @@ +uiPresenter) && $globals->uiPresenter instanceof Presenter; } + + public function shouldBePositionRenderedClientSide(Position $position): bool + { + return false; + } } diff --git a/src/Bridge/Latte/RenderingMode/QueuedRenderingMode.php b/src/Bridge/Latte/RenderingMode/QueuedRenderingMode.php index 32ff3b0..dde6eff 100644 --- a/src/Bridge/Latte/RenderingMode/QueuedRenderingMode.php +++ b/src/Bridge/Latte/RenderingMode/QueuedRenderingMode.php @@ -8,6 +8,13 @@ final class QueuedRenderingMode implements RenderingModeInterface { + public const Name = 'queued'; + + public function getName(): string + { + return self::Name; + } + public function supportsQueues(): bool { return true; @@ -17,4 +24,9 @@ public function shouldBePositionQueued(Position $position, object $globals): boo { return true; } + + public function shouldBePositionRenderedClientSide(Position $position): bool + { + return false; + } } diff --git a/src/Bridge/Latte/RenderingMode/RenderingModeInterface.php b/src/Bridge/Latte/RenderingMode/RenderingModeInterface.php index 6231bf7..d7f8f5e 100644 --- a/src/Bridge/Latte/RenderingMode/RenderingModeInterface.php +++ b/src/Bridge/Latte/RenderingMode/RenderingModeInterface.php @@ -8,7 +8,11 @@ interface RenderingModeInterface { + public function getName(): string; + public function supportsQueues(): bool; public function shouldBePositionQueued(Position $position, object $globals): bool; + + public function shouldBePositionRenderedClientSide(Position $position): bool; } diff --git a/src/Bridge/Nette/DI/AmpClientExtension.php b/src/Bridge/Nette/DI/AmpClientExtension.php index e2f03b5..024451e 100644 --- a/src/Bridge/Nette/DI/AmpClientExtension.php +++ b/src/Bridge/Nette/DI/AmpClientExtension.php @@ -99,6 +99,7 @@ public function getConfigSchema(): Schema 'random' => Expect::string(), 'multiple' => Expect::string(), 'not_found' => Expect::string(), + 'client_side' => Expect::string(), ])->castTo('array'), ])->castTo(RendererConfig::class), ])->castTo(AmpClientConfig::class); @@ -273,10 +274,11 @@ private function resolveRendererBridgeCreator(RendererConfig $config): Statement } $templatesOverride = array_filter([ - Templates::TemplateSingle => $config->templates['single'] ?? null, - Templates::TemplateRandom => $config->templates['random'] ?? null, - Templates::TemplateMultiple => $config->templates['multiple'] ?? null, - Templates::TemplateNotFound => $config->templates['not_found'] ?? null, + Templates::Single => $config->templates['single'] ?? null, + Templates::Random => $config->templates['random'] ?? null, + Templates::Multiple => $config->templates['multiple'] ?? null, + Templates::NotFound => $config->templates['not_found'] ?? null, + Templates::ClientSide => $config->templates['client_side'] ?? null, ]); if (0 < count($templatesOverride)) { diff --git a/src/Bridge/Nette/DI/AmpClientLatteExtension.php b/src/Bridge/Nette/DI/AmpClientLatteExtension.php index e12a794..77115dd 100644 --- a/src/Bridge/Nette/DI/AmpClientLatteExtension.php +++ b/src/Bridge/Nette/DI/AmpClientLatteExtension.php @@ -20,11 +20,13 @@ use SixtyEightPublishers\AmpClient\Bridge\Latte\AmpClientLatteExtension as AmpClientLatteExtensionRegister; use SixtyEightPublishers\AmpClient\Bridge\Latte\Event\ConfigureClientEventHandlerInterface; use SixtyEightPublishers\AmpClient\Bridge\Latte\RendererProvider; +use SixtyEightPublishers\AmpClient\Bridge\Latte\RenderingMode\ClientSideRenderingMode; use SixtyEightPublishers\AmpClient\Bridge\Latte\RenderingMode\DirectRenderingMode; use SixtyEightPublishers\AmpClient\Bridge\Latte\RenderingMode\QueuedRenderingInPresenterContextMode; use SixtyEightPublishers\AmpClient\Bridge\Latte\RenderingMode\QueuedRenderingMode; use SixtyEightPublishers\AmpClient\Bridge\Nette\Application\AttachPresenterHandlersOnApplicationHandler; use SixtyEightPublishers\AmpClient\Bridge\Nette\DI\Config\AmpClientLatteConfig; +use function array_map; use function assert; use function count; use function sprintf; @@ -32,9 +34,10 @@ final class AmpClientLatteExtension extends CompilerExtension { private const RenderingModes = [ - 'direct' => DirectRenderingMode::class, - 'queued' => QueuedRenderingMode::class, - 'queued_in_presenter_context' => QueuedRenderingInPresenterContextMode::class, + DirectRenderingMode::Name => DirectRenderingMode::class, + QueuedRenderingMode::Name => QueuedRenderingMode::class, + QueuedRenderingInPresenterContextMode::Name => QueuedRenderingInPresenterContextMode::class, + ClientSideRenderingMode::Name => ClientSideRenderingMode::class, ]; private bool $debugMode; @@ -50,6 +53,9 @@ public function getConfigSchema(): Schema 'banner_macro_name' => Expect::string('banner'), 'rendering_mode' => Expect::anyOf(Expect::string(), Expect::type(Statement::class)) ->default('direct'), + 'alternative_rendering_modes' => Expect::listOf( + Expect::anyOf(Expect::string(), Expect::type(Statement::class)), + ), ])->castTo(AmpClientLatteConfig::class); } @@ -75,25 +81,26 @@ public function loadConfiguration(): void $config = $this->getConfig(); assert($config instanceof AmpClientLatteConfig); - $renderingMode = $config->rendering_mode; - - if (is_string($renderingMode) && isset(self::RenderingModes[$renderingMode])) { - $renderingMode = self::RenderingModes[$renderingMode]; - } - - if (!($renderingMode instanceof Statement)) { - $renderingMode = new Statement($renderingMode); - } - - $builder->addDefinition($this->prefix('rendererProvider')) + $rendererProviderDefinition = $builder->addDefinition($this->prefix('rendererProvider')) ->setAutowired(false) ->setFactory(RendererProvider::class) ->addSetup('setRenderingMode', [ - 'renderingMode' => $renderingMode, + 'renderingMode' => $this->createRenderingModeStatement($config->rendering_mode), ]) ->addSetup('setDebugMode', [ 'debugMode' => $this->debugMode, ]); + + $alternativeRenderingModes = array_map( + fn ($renderingMode): Statement => $this->createRenderingModeStatement($renderingMode), + $config->alternative_rendering_modes, + ); + + if (0 < count($alternativeRenderingModes)) { + $rendererProviderDefinition->addSetup('setAlternativeRenderingModes', [ + 'renderingModes' => $alternativeRenderingModes, + ]); + } } public function beforeCompile(): void @@ -142,4 +149,20 @@ private function extensionExists(string $classname): bool { return 0 < count($this->compiler->getExtensions($classname)); } + + /** + * @param string|Statement $renderingMode + */ + private function createRenderingModeStatement($renderingMode): Statement + { + if (is_string($renderingMode) && isset(self::RenderingModes[$renderingMode])) { + $renderingMode = self::RenderingModes[$renderingMode]; + } + + if (!($renderingMode instanceof Statement)) { + $renderingMode = new Statement($renderingMode); + } + + return $renderingMode; + } } diff --git a/src/Bridge/Nette/DI/Config/AmpClientLatteConfig.php b/src/Bridge/Nette/DI/Config/AmpClientLatteConfig.php index c8ba8c7..4e6189d 100644 --- a/src/Bridge/Nette/DI/Config/AmpClientLatteConfig.php +++ b/src/Bridge/Nette/DI/Config/AmpClientLatteConfig.php @@ -10,6 +10,9 @@ final class AmpClientLatteConfig { public string $banner_macro_name; - /** @var string|Statement|null */ + /** @var string|Statement */ public $rendering_mode; + + /** @var array */ + public array $alternative_rendering_modes = []; } diff --git a/src/Renderer/Latte/LatteRendererBridge.php b/src/Renderer/Latte/LatteRendererBridge.php index 19d8a67..e0a5250 100644 --- a/src/Renderer/Latte/LatteRendererBridge.php +++ b/src/Renderer/Latte/LatteRendererBridge.php @@ -5,14 +5,17 @@ namespace SixtyEightPublishers\AmpClient\Renderer\Latte; use Latte\Engine; +use SixtyEightPublishers\AmpClient\Renderer\Latte\Templates\ClientSideTemplate; use SixtyEightPublishers\AmpClient\Renderer\Latte\Templates\MultipleTemplate; use SixtyEightPublishers\AmpClient\Renderer\Latte\Templates\NotFoundTemplate; use SixtyEightPublishers\AmpClient\Renderer\Latte\Templates\RandomTemplate; use SixtyEightPublishers\AmpClient\Renderer\Latte\Templates\SingleTemplate; use SixtyEightPublishers\AmpClient\Renderer\RendererBridgeInterface; use SixtyEightPublishers\AmpClient\Renderer\Templates; +use SixtyEightPublishers\AmpClient\Request\ValueObject\Position as RequestPosition; use SixtyEightPublishers\AmpClient\Response\ValueObject\Banner; -use SixtyEightPublishers\AmpClient\Response\ValueObject\Position; +use SixtyEightPublishers\AmpClient\Response\ValueObject\Position as ResponsePosition; +use function implode; final class LatteRendererBridge implements RendererBridgeInterface { @@ -26,10 +29,11 @@ public function __construct(LatteFactoryInterface $latteFactory) { $this->latteFactory = $latteFactory; $this->templates = new Templates([ - Templates::TemplateSingle => __DIR__ . '/Templates/single.latte', - Templates::TemplateRandom => __DIR__ . '/Templates/random.latte', - Templates::TemplateMultiple => __DIR__ . '/Templates/multiple.latte', - Templates::TemplateNotFound => __DIR__ . '/Templates/notFound.latte', + Templates::Single => __DIR__ . '/Templates/single.latte', + Templates::Random => __DIR__ . '/Templates/random.latte', + Templates::Multiple => __DIR__ . '/Templates/multiple.latte', + Templates::NotFound => __DIR__ . '/Templates/notFound.latte', + Templates::ClientSide => __DIR__ . '/Templates/clientSide.latte', ]); } @@ -48,38 +52,52 @@ public function overrideTemplates(Templates $templates): self return $renderer; } - public function renderNotFound(Position $position, array $elementAttributes = []): string + public function renderNotFound(ResponsePosition $position, array $elementAttributes = []): string { return $this->getLatte()->renderToString( - $this->templates->getTemplateFile(Templates::TemplateNotFound), + $this->templates->getTemplateFile(Templates::NotFound), new NotFoundTemplate($position, $elementAttributes), ); } - public function renderSingle(Position $position, ?Banner $banner, array $elementAttributes = []): string + public function renderSingle(ResponsePosition $position, ?Banner $banner, array $elementAttributes = []): string { return $this->getLatte()->renderToString( - $this->templates->getTemplateFile(Templates::TemplateSingle), + $this->templates->getTemplateFile(Templates::Single), new SingleTemplate($position, $banner, $elementAttributes), ); } - public function renderRandom(Position $position, ?Banner $banner, array $elementAttributes = []): string + public function renderRandom(ResponsePosition $position, ?Banner $banner, array $elementAttributes = []): string { return $this->getLatte()->renderToString( - $this->templates->getTemplateFile(Templates::TemplateRandom), + $this->templates->getTemplateFile(Templates::Random), new RandomTemplate($position, $banner, $elementAttributes), ); } - public function renderMultiple(Position $position, array $banners, array $elementAttributes = []): string + public function renderMultiple(ResponsePosition $position, array $banners, array $elementAttributes = []): string { return $this->getLatte()->renderToString( - $this->templates->getTemplateFile(Templates::TemplateMultiple), + $this->templates->getTemplateFile(Templates::Multiple), new MultipleTemplate($position, $banners, $elementAttributes), ); } + public function renderClientSide(RequestPosition $position, array $elementAttributes = []): string + { + $resourceAttributes = []; + + foreach ($position->getResources() as $resource) { + $resourceAttributes['data-amp-resource-' . $resource->getCode()] = implode(',', $resource->getValues()); + } + + return $this->getLatte()->renderToString( + $this->templates->getTemplateFile(Templates::ClientSide), + new ClientSideTemplate($position, $resourceAttributes, $elementAttributes), + ); + } + private function getLatte(): Engine { if (null === $this->latte) { diff --git a/src/Renderer/Latte/Templates/ClientSideTemplate.php b/src/Renderer/Latte/Templates/ClientSideTemplate.php new file mode 100644 index 0000000..2c273dc --- /dev/null +++ b/src/Renderer/Latte/Templates/ClientSideTemplate.php @@ -0,0 +1,32 @@ + */ + public array $resourceAttributes; + + /** @var array */ + public array $elementAttributes; + + /** + * @param array $resourceAttributes + * @param array $elementAttributes + */ + public function __construct( + Position $position, + array $resourceAttributes, + array $elementAttributes + ) { + $this->position = $position; + $this->resourceAttributes = $resourceAttributes; + $this->elementAttributes = $elementAttributes; + } +} diff --git a/src/Renderer/Latte/Templates/clientSide.latte b/src/Renderer/Latte/Templates/clientSide.latte new file mode 100644 index 0000000..feeef15 --- /dev/null +++ b/src/Renderer/Latte/Templates/clientSide.latte @@ -0,0 +1,5 @@ +{templateType SixtyEightPublishers\AmpClient\Renderer\Latte\Templates\ClientSideTemplate} + +
+
diff --git a/src/Renderer/Phtml/PhtmlRendererBridge.php b/src/Renderer/Phtml/PhtmlRendererBridge.php index 1f4467b..9031b48 100644 --- a/src/Renderer/Phtml/PhtmlRendererBridge.php +++ b/src/Renderer/Phtml/PhtmlRendererBridge.php @@ -7,8 +7,9 @@ use SixtyEightPublishers\AmpClient\Renderer\OutputBuffer; use SixtyEightPublishers\AmpClient\Renderer\RendererBridgeInterface; use SixtyEightPublishers\AmpClient\Renderer\Templates; +use SixtyEightPublishers\AmpClient\Request\ValueObject\Position as RequestPosition; use SixtyEightPublishers\AmpClient\Response\ValueObject\Banner; -use SixtyEightPublishers\AmpClient\Response\ValueObject\Position; +use SixtyEightPublishers\AmpClient\Response\ValueObject\Position as ResponsePosition; use Throwable; final class PhtmlRendererBridge implements RendererBridgeInterface @@ -18,10 +19,11 @@ final class PhtmlRendererBridge implements RendererBridgeInterface public function __construct() { $this->templates = new Templates([ - Templates::TemplateSingle => __DIR__ . '/Templates/single.phtml', - Templates::TemplateRandom => __DIR__ . '/Templates/random.phtml', - Templates::TemplateMultiple => __DIR__ . '/Templates/multiple.phtml', - Templates::TemplateNotFound => __DIR__ . '/Templates/notFound.phtml', + Templates::Single => __DIR__ . '/Templates/single.phtml', + Templates::Random => __DIR__ . '/Templates/random.phtml', + Templates::Multiple => __DIR__ . '/Templates/multiple.phtml', + Templates::NotFound => __DIR__ . '/Templates/notFound.phtml', + Templates::ClientSide => __DIR__ . '/Templates/clientSide.phtml', ]); } @@ -36,9 +38,9 @@ public function overrideTemplates(Templates $templates): self /** * @throws Throwable */ - public function renderNotFound(Position $position, array $elementAttributes = []): string + public function renderNotFound(ResponsePosition $position, array $elementAttributes = []): string { - $filename = $this->templates->getTemplateFile(Templates::TemplateNotFound); + $filename = $this->templates->getTemplateFile(Templates::NotFound); return OutputBuffer::capture(function () use ($filename, $position, $elementAttributes) { require $filename; @@ -48,9 +50,9 @@ public function renderNotFound(Position $position, array $elementAttributes = [] /** * @throws Throwable */ - public function renderSingle(Position $position, ?Banner $banner, array $elementAttributes = []): string + public function renderSingle(ResponsePosition $position, ?Banner $banner, array $elementAttributes = []): string { - $filename = $this->templates->getTemplateFile(Templates::TemplateSingle); + $filename = $this->templates->getTemplateFile(Templates::Single); return OutputBuffer::capture(function () use ($filename, $position, $banner, $elementAttributes) { require $filename; @@ -60,9 +62,9 @@ public function renderSingle(Position $position, ?Banner $banner, array $element /** * @throws Throwable */ - public function renderRandom(Position $position, ?Banner $banner, array $elementAttributes = []): string + public function renderRandom(ResponsePosition $position, ?Banner $banner, array $elementAttributes = []): string { - $filename = $this->templates->getTemplateFile(Templates::TemplateRandom); + $filename = $this->templates->getTemplateFile(Templates::Random); return OutputBuffer::capture(function () use ($filename, $position, $banner, $elementAttributes) { require $filename; @@ -72,12 +74,24 @@ public function renderRandom(Position $position, ?Banner $banner, array $element /** * @throws Throwable */ - public function renderMultiple(Position $position, array $banners, array $elementAttributes = []): string + public function renderMultiple(ResponsePosition $position, array $banners, array $elementAttributes = []): string { - $filename = $this->templates->getTemplateFile(Templates::TemplateMultiple); + $filename = $this->templates->getTemplateFile(Templates::Multiple); return OutputBuffer::capture(function () use ($filename, $position, $banners, $elementAttributes) { require $filename; }); } + + /** + * @throws Throwable + */ + public function renderClientSide(RequestPosition $position, array $elementAttributes = []): string + { + $filename = $this->templates->getTemplateFile(Templates::ClientSide); + + return OutputBuffer::capture(function () use ($filename, $position, $elementAttributes) { + require $filename; + }); + } } diff --git a/src/Renderer/Phtml/Templates/clientSide.phtml b/src/Renderer/Phtml/Templates/clientSide.phtml new file mode 100644 index 0000000..84c2ea8 --- /dev/null +++ b/src/Renderer/Phtml/Templates/clientSide.phtml @@ -0,0 +1,15 @@ + $elementAttributes */ +?> +
getResources() as $resource) : ?>getCode() . '="' . implode(',', $resource->getValues()) . '"' ?> + > +
diff --git a/src/Renderer/Phtml/Templates/contents.fragment.phtml b/src/Renderer/Phtml/Templates/contents.fragment.phtml index 58ce465..d4e4232 100644 --- a/src/Renderer/Phtml/Templates/contents.fragment.phtml +++ b/src/Renderer/Phtml/Templates/contents.fragment.phtml @@ -13,14 +13,14 @@ use SixtyEightPublishers\AmpClient\Renderer\BreakpointStyle\BreakpointStyle; /** @var Position $position */ /** @var ?Banner $banner */ ?> -getContents() as $content): ?> +getContents() as $content) : ?> getTarget()) : ?>target="getTarget()) ?>"> - getSources() as $source): ?> + getSources() as $source) : ?> diff --git a/src/Renderer/Phtml/Templates/multiple.phtml b/src/Renderer/Phtml/Templates/multiple.phtml index 890864c..583d717 100644 --- a/src/Renderer/Phtml/Templates/multiple.phtml +++ b/src/Renderer/Phtml/Templates/multiple.phtml @@ -20,7 +20,7 @@ use SixtyEightPublishers\AmpClient\Renderer\AmpBannerExternalAttribute;
- +
diff --git a/src/Renderer/Renderer.php b/src/Renderer/Renderer.php index 8bfeef1..54ce0d3 100644 --- a/src/Renderer/Renderer.php +++ b/src/Renderer/Renderer.php @@ -6,7 +6,8 @@ use SixtyEightPublishers\AmpClient\Exception\RendererException; use SixtyEightPublishers\AmpClient\Renderer\Phtml\PhtmlRendererBridge; -use SixtyEightPublishers\AmpClient\Response\ValueObject\Position; +use SixtyEightPublishers\AmpClient\Request\ValueObject\Position as RequestPosition; +use SixtyEightPublishers\AmpClient\Response\ValueObject\Position as ResponsePosition; use Throwable; use function get_class; @@ -32,25 +33,25 @@ public static function create(?RendererBridgeInterface $rendererBridge = null): ); } - public function render(Position $position, array $elementAttributes = []): string + public function render(ResponsePosition $position, array $elementAttributes = []): string { try { switch ($position->getDisplayType()) { case null: return $this->rendererBridge->renderNotFound($position, $elementAttributes); - case Position::DisplayTypeMultiple: + case ResponsePosition::DisplayTypeMultiple: return $this->rendererBridge->renderMultiple( $position, $this->bannersResolver->resolveMultiple($position), $elementAttributes, ); - case Position::DisplayTypeRandom: + case ResponsePosition::DisplayTypeRandom: return $this->rendererBridge->renderRandom( $position, $this->bannersResolver->resolveRandom($position), $elementAttributes, ); - case Position::DisplayTypeSingle: + case ResponsePosition::DisplayTypeSingle: default: return $this->rendererBridge->renderSingle( $position, @@ -70,4 +71,21 @@ public function render(Position $position, array $elementAttributes = []): strin ); } } + + public function renderClientSide(RequestPosition $position, array $elementAttributes = []): string + { + try { + return $this->rendererBridge->renderClientSide($position, $elementAttributes); + } catch (Throwable $e) { + if ($e instanceof RendererException) { + throw $e; + } + + throw RendererException::rendererBridgeThrownError( + get_class($this->rendererBridge), + $position->getCode(), + $e, + ); + } + } } diff --git a/src/Renderer/RendererBridgeInterface.php b/src/Renderer/RendererBridgeInterface.php index 5f12254..5c6288f 100644 --- a/src/Renderer/RendererBridgeInterface.php +++ b/src/Renderer/RendererBridgeInterface.php @@ -4,8 +4,9 @@ namespace SixtyEightPublishers\AmpClient\Renderer; +use SixtyEightPublishers\AmpClient\Request\ValueObject\Position as RequestPosition; use SixtyEightPublishers\AmpClient\Response\ValueObject\Banner; -use SixtyEightPublishers\AmpClient\Response\ValueObject\Position; +use SixtyEightPublishers\AmpClient\Response\ValueObject\Position as ResponsePosition; interface RendererBridgeInterface { @@ -14,21 +15,26 @@ public function overrideTemplates(Templates $templates): self; /** * @param array $elementAttributes */ - public function renderNotFound(Position $position, array $elementAttributes = []): string; + public function renderNotFound(ResponsePosition $position, array $elementAttributes = []): string; /** * @param array $elementAttributes */ - public function renderSingle(Position $position, ?Banner $banner, array $elementAttributes = []): string; + public function renderSingle(ResponsePosition $position, ?Banner $banner, array $elementAttributes = []): string; /** * @param array $elementAttributes */ - public function renderRandom(Position $position, ?Banner $banner, array $elementAttributes = []): string; + public function renderRandom(ResponsePosition $position, ?Banner $banner, array $elementAttributes = []): string; /** * @param array $banners * @param array $elementAttributes */ - public function renderMultiple(Position $position, array $banners, array $elementAttributes = []): string; + public function renderMultiple(ResponsePosition $position, array $banners, array $elementAttributes = []): string; + + /** + * @param array $elementAttributes + */ + public function renderClientSide(RequestPosition $position, array $elementAttributes = []): string; } diff --git a/src/Renderer/RendererInterface.php b/src/Renderer/RendererInterface.php index b928ef4..d977788 100644 --- a/src/Renderer/RendererInterface.php +++ b/src/Renderer/RendererInterface.php @@ -5,7 +5,8 @@ namespace SixtyEightPublishers\AmpClient\Renderer; use SixtyEightPublishers\AmpClient\Exception\RendererException; -use SixtyEightPublishers\AmpClient\Response\ValueObject\Position; +use SixtyEightPublishers\AmpClient\Request\ValueObject\Position as RequestPosition; +use SixtyEightPublishers\AmpClient\Response\ValueObject\Position as ResponsePosition; interface RendererInterface { @@ -14,5 +15,12 @@ interface RendererInterface * * @throws RendererException */ - public function render(Position $position, array $elementAttributes = []): string; + public function render(ResponsePosition $position, array $elementAttributes = []): string; + + /** + * @param array $elementAttributes + * + * @throws RendererException + */ + public function renderClientSide(RequestPosition $position, array $elementAttributes = []): string; } diff --git a/src/Renderer/Templates.php b/src/Renderer/Templates.php index b40f489..7f350f1 100644 --- a/src/Renderer/Templates.php +++ b/src/Renderer/Templates.php @@ -9,17 +9,19 @@ final class Templates { - public const TemplateSingle = 'single'; - public const TemplateRandom = 'random'; - public const TemplateMultiple = 'multiple'; - public const TemplateNotFound = 'notFound'; + public const Single = 'single'; + public const Random = 'random'; + public const Multiple = 'multiple'; + public const NotFound = 'notFound'; + public const ClientSide = 'clientSide'; /** * @var array{ * single?: string, * random?: string, * multiple?: string, - * 'notFound'?: string, + * notFound?: string, + * clientSide?: string, * } */ private array $filesMap; @@ -30,6 +32,7 @@ final class Templates * random?: string, * multiple?: string, * notFound?: string, + * clientSide?: string, * } $filesMap */ public function __construct(array $filesMap) diff --git a/tests/Bridge/Latte/AmpClientLatteExtensionTest.php b/tests/Bridge/Latte/AmpClientLatteExtensionTest.php index cf487e0..f5f5e24 100644 --- a/tests/Bridge/Latte/AmpClientLatteExtensionTest.php +++ b/tests/Bridge/Latte/AmpClientLatteExtensionTest.php @@ -116,6 +116,70 @@ public function latteTemplatesDataProvider(): array ], 2 => null, ], + 'Attributes as array' => [ + 0 => <<<'LATTE' + {banner homepage.top, attributes: ['class' => 'test-class']} + LATTE, + 1 => [ + 'homepage.top', + [ + 'attributes' => ['class' => 'test-class'], + ], + ], + 2 => null, + ], + 'Attributes as variable' => [ + 0 => <<<'LATTE' + {var $attributes = ['class' => 'test-class']} + {banner homepage.top, attributes: $attributes} + LATTE, + 1 => [ + 'homepage.top', + [ + 'attributes' => ['class' => 'test-class'], + ], + ], + 2 => null, + ], + 'Mode as string' => [ + 0 => <<<'LATTE' + {banner homepage.top, mode: 'client_side'} + LATTE, + 1 => [ + 'homepage.top', + [ + 'mode' => 'client_side', + ], + ], + 2 => null, + ], + 'Mode as variable' => [ + 0 => <<<'LATTE' + {var $mode = 'client_side'} + {banner homepage.top, mode: $mode} + LATTE, + 1 => [ + 'homepage.top', + [ + 'mode' => 'client_side', + ], + ], + 2 => null, + ], + 'Full featured' => [ + 0 => <<<'LATTE' + {banner homepage.top, resources: [product => '123', category => ['123', '456']], attributes: ['class' => 'test-class'], mode: 'client_side'} + LATTE, + 1 => [ + 'homepage.top', + [ + 'resources' => ['product' => '123', 'category' => ['123', '456']], + 'attributes' => ['class' => 'test-class'], + 'mode' => 'client_side', + ], + ], + 2 => null, + ], ]; } diff --git a/tests/Bridge/Latte/RendererProviderTest.php b/tests/Bridge/Latte/RendererProviderTest.php index b91855b..dcfb660 100644 --- a/tests/Bridge/Latte/RendererProviderTest.php +++ b/tests/Bridge/Latte/RendererProviderTest.php @@ -4,6 +4,7 @@ namespace SixtyEightPublishers\AmpClient\Tests\Bridge\Latte; +use InvalidArgumentException; use Mockery; use Psr\Log\LoggerInterface; use SixtyEightPublishers\AmpClient\AmpClientInterface; @@ -62,6 +63,41 @@ public function testInvokingDefaultInstanceWithoutResources(): void Assert::same('', $provider(new stdClass(), 'homepage.top')); } + public function testInvokingDefaultInstanceWithSameModeAsDefault(): void + { + $client = Mockery::mock(AmpClientInterface::class); + $renderer = Mockery::mock(RendererInterface::class); + $provider = new RendererProvider($client, $renderer); + + $responsePosition = new ResponsePosition('1234', 'homepage.top', 'Homepage top', 0, ResponsePosition::DisplayTypeSingle, ResponsePosition::BreakpointTypeMin, []); + $response = new BannersResponse([ + 'homepage.top' => $responsePosition, + ]); + + $client + ->shouldReceive('fetchBanners') + ->once() + ->with(Mockery::type(BannersRequest::class)) + ->andReturnUsing(static function (BannersRequest $request) use ($response): BannersResponse { + Assert::equal( + new BannersRequest([ + new RequestPosition('homepage.top'), + ]), + $request, + ); + + return $response; + }); + + $renderer + ->shouldReceive('render') + ->once() + ->with($responsePosition, []) + ->andReturn(''); + + Assert::same('', $provider(new stdClass(), 'homepage.top', ['mode' => 'direct'])); + } + public function testInvokingDefaultInstanceWithAttributes(): void { $client = Mockery::mock(AmpClientInterface::class); @@ -360,6 +396,22 @@ public function testPositionsShouldBeQueuedAndReplacedInStringOutput(): void $provider->setRenderingMode($renderingMode); $renderingMode + ->shouldReceive('shouldBePositionRenderedClientSide') + ->once() + ->with(Mockery::type(RequestPosition::class)) + ->andReturnUsing(static function (RequestPosition $position) use ($requestPosition1): bool { + Assert::equal($requestPosition1, $position); + + return false; + }) + ->shouldReceive('shouldBePositionRenderedClientSide') + ->once() + ->with(Mockery::type(RequestPosition::class)) + ->andReturnUsing(static function (RequestPosition $position) use ($requestPosition2): bool { + Assert::equal($requestPosition2, $position); + + return false; + }) ->shouldReceive('shouldBePositionQueued') ->once() ->with(Mockery::type(RequestPosition::class), $globals) @@ -436,6 +488,22 @@ public function testPositionsShouldBeQueuedAndReplacedInArrayOutput(): void $provider->setRenderingMode($renderingMode); $renderingMode + ->shouldReceive('shouldBePositionRenderedClientSide') + ->once() + ->with(Mockery::type(RequestPosition::class)) + ->andReturnUsing(static function (RequestPosition $position) use ($requestPosition1): bool { + Assert::equal($requestPosition1, $position); + + return false; + }) + ->shouldReceive('shouldBePositionRenderedClientSide') + ->once() + ->with(Mockery::type(RequestPosition::class)) + ->andReturnUsing(static function (RequestPosition $position) use ($requestPosition2): bool { + Assert::equal($requestPosition2, $position); + + return false; + }) ->shouldReceive('shouldBePositionQueued') ->once() ->with(Mockery::type(RequestPosition::class), $globals) @@ -504,6 +572,94 @@ public function testPositionsShouldBeQueuedAndReplacedInArrayOutput(): void ); } + public function testPositionShouldBeRenderedClientSide(): void + { + $client = Mockery::mock(AmpClientInterface::class); + $renderer = Mockery::mock(RendererInterface::class); + $clientSideRenderingMode = Mockery::mock(RenderingModeInterface::class); + $requestPosition = new RequestPosition('homepage.top'); + + $provider = new RendererProvider($client, $renderer); + + $provider->setRenderingMode($clientSideRenderingMode); + + $clientSideRenderingMode + ->shouldReceive('shouldBePositionRenderedClientSide') + ->once() + ->with(Mockery::type(RequestPosition::class)) + ->andReturnUsing(static function (RequestPosition $position) use ($requestPosition): bool { + Assert::equal($requestPosition, $position); + + return true; + }); + + $renderer + ->shouldReceive('renderClientSide') + ->once() + ->with(Mockery::type(RequestPosition::class), []) + ->andReturnUsing(static function (RequestPosition $position) use ($requestPosition): string { + Assert::equal($requestPosition, $position); + + return ''; + }); + + Assert::same('', $provider(new stdClass(), 'homepage.top')); + } + + public function testPositionShouldBeRenderedClientSideWithAlternativeMode(): void + { + $client = Mockery::mock(AmpClientInterface::class); + $renderer = Mockery::mock(RendererInterface::class); + $clientSideRenderingMode = Mockery::mock(RenderingModeInterface::class); + $requestPosition = new RequestPosition('homepage.top'); + + $provider = new RendererProvider($client, $renderer); + + $clientSideRenderingMode + ->shouldReceive('getName') + ->once() + ->withNoArgs() + ->andReturn('client_side'); + + $clientSideRenderingMode + ->shouldReceive('shouldBePositionRenderedClientSide') + ->once() + ->with(Mockery::type(RequestPosition::class)) + ->andReturnUsing(static function (RequestPosition $position) use ($requestPosition): bool { + Assert::equal($requestPosition, $position); + + return true; + }); + + $renderer + ->shouldReceive('renderClientSide') + ->once() + ->with(Mockery::type(RequestPosition::class), []) + ->andReturnUsing(static function (RequestPosition $position) use ($requestPosition): string { + Assert::equal($requestPosition, $position); + + return ''; + }); + + $provider->setAlternativeRenderingModes([$clientSideRenderingMode]); + + Assert::same('', $provider(new stdClass(), 'homepage.top', ['mode' => 'client_side'])); + } + + public function testExceptionShouldBeThrownWhenProviderIsInvokedWithModeThatIsNotRegisteredBetweenAlternativeModes(): void + { + $client = Mockery::mock(AmpClientInterface::class); + $renderer = Mockery::mock(RendererInterface::class); + + $provider = new RendererProvider($client, $renderer); + + Assert::exception( + static fn () => $provider(new stdClass(), 'homepage.top', ['mode' => 'test']), + InvalidArgumentException::class, + 'Invalid value for option "mode". The value "test" is not registered between alternative rendering modes.', + ); + } + protected function tearDown(): void { Mockery::close(); diff --git a/tests/Bridge/Latte/RenderingMode/ClientSideRenderingModeTest.php b/tests/Bridge/Latte/RenderingMode/ClientSideRenderingModeTest.php new file mode 100644 index 0000000..70cdff5 --- /dev/null +++ b/tests/Bridge/Latte/RenderingMode/ClientSideRenderingModeTest.php @@ -0,0 +1,29 @@ +getName()); + Assert::false($mode->supportsQueues()); + Assert::true($mode->shouldBePositionRenderedClientSide($position)); + Assert::false($mode->shouldBePositionQueued($position, (object) [])); + } +} + +(new ClientSideRenderingModeTest())->run(); diff --git a/tests/Bridge/Latte/RenderingMode/DirectRenderingModeTest.php b/tests/Bridge/Latte/RenderingMode/DirectRenderingModeTest.php new file mode 100644 index 0000000..6c4f99f --- /dev/null +++ b/tests/Bridge/Latte/RenderingMode/DirectRenderingModeTest.php @@ -0,0 +1,29 @@ +getName()); + Assert::false($mode->supportsQueues()); + Assert::false($mode->shouldBePositionRenderedClientSide($position)); + Assert::false($mode->shouldBePositionQueued($position, (object) [])); + } +} + +(new DirectRenderingModeTest())->run(); diff --git a/tests/Bridge/Latte/RenderingMode/QueuedRenderingInPresenterContextModeTest.php b/tests/Bridge/Latte/RenderingMode/QueuedRenderingInPresenterContextModeTest.php new file mode 100644 index 0000000..c36cdf6 --- /dev/null +++ b/tests/Bridge/Latte/RenderingMode/QueuedRenderingInPresenterContextModeTest.php @@ -0,0 +1,34 @@ +getName()); + Assert::true($mode->supportsQueues()); + Assert::false($mode->shouldBePositionRenderedClientSide($position)); + + Assert::false($mode->shouldBePositionQueued($position, (object) [])); + Assert::true($mode->shouldBePositionQueued($position, (object) [ + 'uiPresenter' => new class extends Presenter {}, + ])); + } +} + +(new QueuedRenderingInPresenterContextModeTest())->run(); diff --git a/tests/Bridge/Latte/RenderingMode/QueuedRenderingModeTest.php b/tests/Bridge/Latte/RenderingMode/QueuedRenderingModeTest.php new file mode 100644 index 0000000..3831ab2 --- /dev/null +++ b/tests/Bridge/Latte/RenderingMode/QueuedRenderingModeTest.php @@ -0,0 +1,29 @@ +getName()); + Assert::true($mode->supportsQueues()); + Assert::false($mode->shouldBePositionRenderedClientSide($position)); + Assert::true($mode->shouldBePositionQueued($position, (object) [])); + } +} + +(new QueuedRenderingModeTest())->run(); diff --git a/tests/Bridge/Nette/DI/AmpClientExtensionTest.php b/tests/Bridge/Nette/DI/AmpClientExtensionTest.php index 55f6995..8f1a03b 100644 --- a/tests/Bridge/Nette/DI/AmpClientExtensionTest.php +++ b/tests/Bridge/Nette/DI/AmpClientExtensionTest.php @@ -137,10 +137,11 @@ public function testContainerWithRendererTemplates(): void $templates = call_user_func(Closure::bind(static fn (): Templates => $rendererBridge->templates, null, PhtmlRendererBridge::class)); assert($templates instanceof Templates); - Assert::same(__DIR__ . '/../../../resources/renderer/single/templates/single1.phtml', $templates->getTemplateFile(Templates::TemplateSingle)); - Assert::same(__DIR__ . '/../../../resources/renderer/random/templates/random1.phtml', $templates->getTemplateFile(Templates::TemplateRandom)); - Assert::same(__DIR__ . '/../../../resources/renderer/multiple/templates/multiple1.phtml', $templates->getTemplateFile(Templates::TemplateMultiple)); - Assert::same(__DIR__ . '/../../../resources/renderer/not-found/templates/not-found1.phtml', $templates->getTemplateFile(Templates::TemplateNotFound)); + Assert::same(__DIR__ . '/../../../resources/renderer/single/templates/single1.phtml', $templates->getTemplateFile(Templates::Single)); + Assert::same(__DIR__ . '/../../../resources/renderer/random/templates/random1.phtml', $templates->getTemplateFile(Templates::Random)); + Assert::same(__DIR__ . '/../../../resources/renderer/multiple/templates/multiple1.phtml', $templates->getTemplateFile(Templates::Multiple)); + Assert::same(__DIR__ . '/../../../resources/renderer/not-found/templates/not-found1.phtml', $templates->getTemplateFile(Templates::NotFound)); + Assert::same(__DIR__ . '/../../../resources/renderer/client-side/templates/client-side1.phtml', $templates->getTemplateFile(Templates::ClientSide)); } public function testContainerWithMethodOption(): void diff --git a/tests/Bridge/Nette/DI/AmpClientLatteExtensionTest.php b/tests/Bridge/Nette/DI/AmpClientLatteExtensionTest.php index 5bbf2be..123cda2 100644 --- a/tests/Bridge/Nette/DI/AmpClientLatteExtensionTest.php +++ b/tests/Bridge/Nette/DI/AmpClientLatteExtensionTest.php @@ -12,6 +12,7 @@ use RuntimeException; use SixtyEightPublishers\AmpClient\Bridge\Latte\Event\ConfigureClientEventHandlerInterface; use SixtyEightPublishers\AmpClient\Bridge\Latte\RendererProvider; +use SixtyEightPublishers\AmpClient\Bridge\Latte\RenderingMode\ClientSideRenderingMode; use SixtyEightPublishers\AmpClient\Bridge\Latte\RenderingMode\DirectRenderingMode; use SixtyEightPublishers\AmpClient\Bridge\Latte\RenderingMode\QueuedRenderingInPresenterContextMode; use SixtyEightPublishers\AmpClient\Bridge\Latte\RenderingMode\QueuedRenderingMode; @@ -53,9 +54,6 @@ public function testContainerWithMinimalConfiguration(): void $container, false, new DirectRenderingMode(), - [ - ConfigureClientEventHandlerInterface::class => [], - ], ); } @@ -67,9 +65,6 @@ public function testContainerWithMinimalConfigurationAndApplicationEnabled(): vo $container, false, new DirectRenderingMode(), - [ - ConfigureClientEventHandlerInterface::class => [], - ], ); $this->assertApplicationHandlerAttached($container); @@ -83,9 +78,6 @@ public function testContainerWithDebugMode(): void $container, true, new DirectRenderingMode(), - [ - ConfigureClientEventHandlerInterface::class => [], - ], ); } @@ -97,9 +89,6 @@ public function testContainerWithDirectRenderingModeAsString(): void $container, false, new DirectRenderingMode(), - [ - ConfigureClientEventHandlerInterface::class => [], - ], ); } @@ -111,9 +100,6 @@ public function testContainerWithDirectRenderingModeAsClassname(): void $container, false, new DirectRenderingMode(), - [ - ConfigureClientEventHandlerInterface::class => [], - ], ); } @@ -125,9 +111,6 @@ public function testContainerWithDirectRenderingModeAsStatement(): void $container, false, new DirectRenderingMode(), - [ - ConfigureClientEventHandlerInterface::class => [], - ], ); } @@ -139,9 +122,6 @@ public function testContainerWithQueuedRenderingModeAsString(): void $container, false, new QueuedRenderingMode(), - [ - ConfigureClientEventHandlerInterface::class => [], - ], ); } @@ -153,9 +133,6 @@ public function testContainerWithQueuedRenderingModeAsClassname(): void $container, false, new QueuedRenderingMode(), - [ - ConfigureClientEventHandlerInterface::class => [], - ], ); } @@ -167,9 +144,6 @@ public function testContainerWithQueuedRenderingModeAsStatement(): void $container, false, new QueuedRenderingMode(), - [ - ConfigureClientEventHandlerInterface::class => [], - ], ); } @@ -181,9 +155,6 @@ public function testContainerWithQueuedRenderingInPresenterModeModeAsString(): v $container, false, new QueuedRenderingInPresenterContextMode(), - [ - ConfigureClientEventHandlerInterface::class => [], - ], ); } @@ -195,9 +166,6 @@ public function testContainerWithQueuedRenderingInPresenterModeModeAsClassname() $container, false, new QueuedRenderingInPresenterContextMode(), - [ - ConfigureClientEventHandlerInterface::class => [], - ], ); } @@ -209,8 +177,54 @@ public function testContainerWithQueuedRenderingInPresenterModeModeAsStatement() $container, false, new QueuedRenderingInPresenterContextMode(), + ); + } + + public function testContainerWithClientSideRenderingInPresenterModeModeAsString(): void + { + $container = ContainerFactory::create(__DIR__ . '/Config/AmpClientLatteExtension/config.withClientSideRenderingModeAsString.neon', ['latte']); + + $this->assertLatteExtension( + $container, + false, + new ClientSideRenderingMode(), + ); + } + + public function testContainerWithClientSideRenderingInPresenterModeModeAsClassname(): void + { + $container = ContainerFactory::create(__DIR__ . '/Config/AmpClientLatteExtension/config.withClientSideRenderingModeAsClassname.neon', ['latte']); + + $this->assertLatteExtension( + $container, + false, + new ClientSideRenderingMode(), + ); + } + + public function testContainerWithClientSideRenderingInPresenterModeModeAsStatement(): void + { + $container = ContainerFactory::create(__DIR__ . '/Config/AmpClientLatteExtension/config.withClientSideRenderingModeAsStatement.neon', ['latte']); + + $this->assertLatteExtension( + $container, + false, + new ClientSideRenderingMode(), + ); + } + + public function testContainerWithAlternativeRenderingModes(): void + { + $container = ContainerFactory::create(__DIR__ . '/Config/AmpClientLatteExtension/config.withAlternativeRenderingModes.neon', ['latte']); + + $this->assertLatteExtension( + $container, + false, + new DirectRenderingMode(), [ - ConfigureClientEventHandlerInterface::class => [], + ClientSideRenderingMode::Name => new ClientSideRenderingMode(), + QueuedRenderingMode::Name => new QueuedRenderingMode(), + QueuedRenderingInPresenterContextMode::Name => new QueuedRenderingInPresenterContextMode(), ], ); } @@ -223,6 +237,7 @@ public function testContainerWithConfigureClientEventHandler(): void $container, false, new DirectRenderingMode(), + [], [ ConfigureClientEventHandlerInterface::class => [ new ConfigureClientEventHandlerFixture(null), @@ -231,8 +246,19 @@ public function testContainerWithConfigureClientEventHandler(): void ); } - private function assertLatteExtension(Container $container, bool $debugMode, RenderingModeInterface $renderingMode, array $eventHandlers): void - { + private function assertLatteExtension( + Container $container, + bool $debugMode, + RenderingModeInterface $renderingMode, + array $alternativeRenderingModes = [], + ?array $eventHandlers = null + ): void { + if (null === $eventHandlers) { + $eventHandlers = [ + ConfigureClientEventHandlerInterface::class => [], + ]; + } + $latteFactory = $container->getByType(class_exists(LatteFactory::class) ? LatteFactory::class : ILatteFactory::class); $latte = $latteFactory->create(); $providers = $latte->getProviders(); @@ -244,9 +270,10 @@ private function assertLatteExtension(Container $container, bool $debugMode, Ren Assert::type(RendererProvider::class, $provider); call_user_func(Closure::bind( - static function () use ($provider, $debugMode, $renderingMode, $eventHandlers): void { + static function () use ($provider, $debugMode, $renderingMode, $alternativeRenderingModes, $eventHandlers): void { Assert::same($debugMode, $provider->debugMode); Assert::equal($renderingMode, $provider->renderingMode); + Assert::equal($alternativeRenderingModes, $provider->alternativeRenderingModes); Assert::equal($eventHandlers, $provider->eventHandlers); }, null, diff --git a/tests/Bridge/Nette/DI/Config/AmpClientExtension/config.withRendererTemplates.neon b/tests/Bridge/Nette/DI/Config/AmpClientExtension/config.withRendererTemplates.neon index c58b89e..c20d0f6 100644 --- a/tests/Bridge/Nette/DI/Config/AmpClientExtension/config.withRendererTemplates.neon +++ b/tests/Bridge/Nette/DI/Config/AmpClientExtension/config.withRendererTemplates.neon @@ -11,3 +11,4 @@ amp_client: random: %resources%/renderer/random/templates/random1.phtml multiple: %resources%/renderer/multiple/templates/multiple1.phtml not_found: %resources%/renderer/not-found/templates/not-found1.phtml + client_side: %resources%/renderer/client-side/templates/client-side1.phtml diff --git a/tests/Bridge/Nette/DI/Config/AmpClientLatteExtension/config.withAlternativeRenderingModes.neon b/tests/Bridge/Nette/DI/Config/AmpClientLatteExtension/config.withAlternativeRenderingModes.neon new file mode 100644 index 0000000..13d52f5 --- /dev/null +++ b/tests/Bridge/Nette/DI/Config/AmpClientLatteExtension/config.withAlternativeRenderingModes.neon @@ -0,0 +1,13 @@ +extensions: + amp_client: SixtyEightPublishers\AmpClient\Bridge\Nette\DI\AmpClientExtension + amp_client.latte: SixtyEightPublishers\AmpClient\Bridge\Nette\DI\AmpClientLatteExtension + +amp_client: + url: https://www.example.com + channel: test + +amp_client.latte: + alternative_rendering_modes: + - client_side + - SixtyEightPublishers\AmpClient\Bridge\Latte\RenderingMode\QueuedRenderingMode + - SixtyEightPublishers\AmpClient\Bridge\Latte\RenderingMode\QueuedRenderingInPresenterContextMode() diff --git a/tests/Bridge/Nette/DI/Config/AmpClientLatteExtension/config.withClientSideRenderingModeAsClassname.neon b/tests/Bridge/Nette/DI/Config/AmpClientLatteExtension/config.withClientSideRenderingModeAsClassname.neon new file mode 100644 index 0000000..478f947 --- /dev/null +++ b/tests/Bridge/Nette/DI/Config/AmpClientLatteExtension/config.withClientSideRenderingModeAsClassname.neon @@ -0,0 +1,10 @@ +extensions: + amp_client: SixtyEightPublishers\AmpClient\Bridge\Nette\DI\AmpClientExtension + amp_client.latte: SixtyEightPublishers\AmpClient\Bridge\Nette\DI\AmpClientLatteExtension + +amp_client: + url: https://www.example.com + channel: test + +amp_client.latte: + rendering_mode: SixtyEightPublishers\AmpClient\Bridge\Latte\RenderingMode\ClientSideRenderingMode diff --git a/tests/Bridge/Nette/DI/Config/AmpClientLatteExtension/config.withClientSideRenderingModeAsStatement.neon b/tests/Bridge/Nette/DI/Config/AmpClientLatteExtension/config.withClientSideRenderingModeAsStatement.neon new file mode 100644 index 0000000..bd83df6 --- /dev/null +++ b/tests/Bridge/Nette/DI/Config/AmpClientLatteExtension/config.withClientSideRenderingModeAsStatement.neon @@ -0,0 +1,10 @@ +extensions: + amp_client: SixtyEightPublishers\AmpClient\Bridge\Nette\DI\AmpClientExtension + amp_client.latte: SixtyEightPublishers\AmpClient\Bridge\Nette\DI\AmpClientLatteExtension + +amp_client: + url: https://www.example.com + channel: test + +amp_client.latte: + rendering_mode: SixtyEightPublishers\AmpClient\Bridge\Latte\RenderingMode\ClientSideRenderingMode() diff --git a/tests/Bridge/Nette/DI/Config/AmpClientLatteExtension/config.withClientSideRenderingModeAsString.neon b/tests/Bridge/Nette/DI/Config/AmpClientLatteExtension/config.withClientSideRenderingModeAsString.neon new file mode 100644 index 0000000..4d6416c --- /dev/null +++ b/tests/Bridge/Nette/DI/Config/AmpClientLatteExtension/config.withClientSideRenderingModeAsString.neon @@ -0,0 +1,10 @@ +extensions: + amp_client: SixtyEightPublishers\AmpClient\Bridge\Nette\DI\AmpClientExtension + amp_client.latte: SixtyEightPublishers\AmpClient\Bridge\Nette\DI\AmpClientLatteExtension + +amp_client: + url: https://www.example.com + channel: test + +amp_client.latte: + rendering_mode: client_side diff --git a/tests/Renderer/Latte/LatteRendererBridgeTest.php b/tests/Renderer/Latte/LatteRendererBridgeTest.php index 251524f..8a81229 100644 --- a/tests/Renderer/Latte/LatteRendererBridgeTest.php +++ b/tests/Renderer/Latte/LatteRendererBridgeTest.php @@ -8,8 +8,9 @@ use Latte\Engine; use SixtyEightPublishers\AmpClient\Renderer\Latte\LatteRendererBridge; use SixtyEightPublishers\AmpClient\Renderer\Templates; +use SixtyEightPublishers\AmpClient\Request\ValueObject\Position as RequestPosition; use SixtyEightPublishers\AmpClient\Response\ValueObject\Banner; -use SixtyEightPublishers\AmpClient\Response\ValueObject\Position; +use SixtyEightPublishers\AmpClient\Response\ValueObject\Position as ResponsePosition; use SixtyEightPublishers\AmpClient\Tests\Renderer\AssertHtml; use Tester\Assert; use Tester\TestCase; @@ -23,7 +24,7 @@ public function testTemplatesShouldBeOverridden(): void { $renderer = $this->createRendererBridge(); $modifiedRenderer = $renderer->overrideTemplates(new Templates([ - Templates::TemplateSingle => '/path/to/file', + Templates::Single => '/path/to/file', ])); $originalTemplates = call_user_func(Closure::bind(static fn () => $renderer->templates, null, LatteRendererBridge::class)); @@ -37,7 +38,7 @@ public function testTemplatesShouldBeOverridden(): void * @dataProvider notFoundTemplateDataProvider */ public function testNotFoundTemplateRendering( - Position $position, + ResponsePosition $position, array $elementAttributes, string $expectationFile ): void { @@ -50,7 +51,7 @@ public function testNotFoundTemplateRendering( * @dataProvider singleTemplateDataProvider */ public function testSingleTemplateRendering( - Position $position, + ResponsePosition $position, ?Banner $banner, array $elementAttributes, string $expectationFile @@ -64,7 +65,7 @@ public function testSingleTemplateRendering( * @dataProvider randomTemplateDataProvider */ public function testRandomTemplateRendering( - Position $position, + ResponsePosition $position, ?Banner $banner, array $elementAttributes, string $expectationFile @@ -78,7 +79,7 @@ public function testRandomTemplateRendering( * @dataProvider multipleTemplateDataProvider * */ public function testMultipleTemplateRendering( - Position $position, + ResponsePosition $position, array $banners, array $elementAttributes, string $expectationFile @@ -88,6 +89,19 @@ public function testMultipleTemplateRendering( AssertHtml::assert($expectationFile, $renderer->renderMultiple($position, $banners, $elementAttributes)); } + /** + * @dataProvider clientSideTemplateDataProvider + */ + public function testClientSideTemplateRendering( + RequestPosition $position, + array $elementAttributes, + string $expectationFile + ): void { + $renderer = $this->createRendererBridge(); + + AssertHtml::assert($expectationFile, $renderer->renderClientSide($position, $elementAttributes)); + } + public function notFoundTemplateDataProvider(): array { return require __DIR__ . '/../../resources/renderer/not-found/data-provider.php'; @@ -108,6 +122,11 @@ public function multipleTemplateDataProvider(): array return require __DIR__ . '/../../resources/renderer/multiple/data-provider.php'; } + public function clientSideTemplateDataProvider(): array + { + return require __DIR__ . '/../../resources/renderer/client-side/data-provider.php'; + } + private function createRendererBridge(): LatteRendererBridge { return LatteRendererBridge::fromEngine(new Engine()); diff --git a/tests/Renderer/Phtml/PhtmlRendererBridgeTest.php b/tests/Renderer/Phtml/PhtmlRendererBridgeTest.php index 593dbfc..2c70a31 100644 --- a/tests/Renderer/Phtml/PhtmlRendererBridgeTest.php +++ b/tests/Renderer/Phtml/PhtmlRendererBridgeTest.php @@ -7,8 +7,9 @@ use Closure; use SixtyEightPublishers\AmpClient\Renderer\Phtml\PhtmlRendererBridge; use SixtyEightPublishers\AmpClient\Renderer\Templates; +use SixtyEightPublishers\AmpClient\Request\ValueObject\Position as RequestPosition; use SixtyEightPublishers\AmpClient\Response\ValueObject\Banner; -use SixtyEightPublishers\AmpClient\Response\ValueObject\Position; +use SixtyEightPublishers\AmpClient\Response\ValueObject\Position as ResponsePosition; use SixtyEightPublishers\AmpClient\Tests\Renderer\AssertHtml; use Tester\Assert; use Tester\TestCase; @@ -22,7 +23,7 @@ public function testTemplatesShouldBeOverridden(): void { $renderer = new PhtmlRendererBridge(); $modifiedRenderer = $renderer->overrideTemplates(new Templates([ - Templates::TemplateSingle => '/path/to/file', + Templates::Single => '/path/to/file', ])); $originalTemplates = call_user_func(Closure::bind(static fn () => $renderer->templates, null, PhtmlRendererBridge::class)); @@ -36,7 +37,7 @@ public function testTemplatesShouldBeOverridden(): void * @dataProvider notFoundTemplateDataProvider */ public function testNotFoundTemplateRendering( - Position $position, + ResponsePosition $position, array $elementAttributes, string $expectationFile ): void { @@ -49,7 +50,7 @@ public function testNotFoundTemplateRendering( * @dataProvider singleTemplateDataProvider */ public function testSingleTemplateRendering( - Position $position, + ResponsePosition $position, ?Banner $banner, array $elementAttributes, string $expectationFile @@ -63,7 +64,7 @@ public function testSingleTemplateRendering( * @dataProvider randomTemplateDataProvider */ public function testRandomTemplateRendering( - Position $position, + ResponsePosition $position, ?Banner $banner, array $elementAttributes, string $expectationFile @@ -77,7 +78,7 @@ public function testRandomTemplateRendering( * @dataProvider multipleTemplateDataProvider */ public function testMultipleTemplateRendering( - Position $position, + ResponsePosition $position, array $banners, array $elementAttributes, string $expectationFile @@ -87,6 +88,19 @@ public function testMultipleTemplateRendering( AssertHtml::assert($expectationFile, $renderer->renderMultiple($position, $banners, $elementAttributes)); } + /** + * @dataProvider clientSideTemplateDataProvider + */ + public function testClientSideTemplateRendering( + RequestPosition $position, + array $elementAttributes, + string $expectationFile + ): void { + $renderer = new PhtmlRendererBridge(); + + AssertHtml::assert($expectationFile, $renderer->renderClientSide($position, $elementAttributes)); + } + public function notFoundTemplateDataProvider(): array { return require __DIR__ . '/../../resources/renderer/not-found/data-provider.php'; @@ -106,6 +120,11 @@ public function multipleTemplateDataProvider(): array { return require __DIR__ . '/../../resources/renderer/multiple/data-provider.php'; } + + public function clientSideTemplateDataProvider(): array + { + return require __DIR__ . '/../../resources/renderer/client-side/data-provider.php'; + } } (new PhtmlRendererBridgeTest())->run(); diff --git a/tests/Renderer/RendererTest.php b/tests/Renderer/RendererTest.php index 5ed981c..6bf4e3a 100644 --- a/tests/Renderer/RendererTest.php +++ b/tests/Renderer/RendererTest.php @@ -13,8 +13,10 @@ use SixtyEightPublishers\AmpClient\Renderer\Phtml\PhtmlRendererBridge; use SixtyEightPublishers\AmpClient\Renderer\Renderer; use SixtyEightPublishers\AmpClient\Renderer\RendererBridgeInterface; +use SixtyEightPublishers\AmpClient\Request\ValueObject\BannerResource; +use SixtyEightPublishers\AmpClient\Request\ValueObject\Position as RequestPosition; use SixtyEightPublishers\AmpClient\Response\ValueObject\Banner; -use SixtyEightPublishers\AmpClient\Response\ValueObject\Position; +use SixtyEightPublishers\AmpClient\Response\ValueObject\Position as ResponsePosition; use Tester\Assert; use Tester\TestCase; use function call_user_func; @@ -44,7 +46,7 @@ public function testNotFoundTemplateShouldBeRendered(): void $rendererBridge = Mockery::mock(RendererBridgeInterface::class); $renderer = new Renderer($bannersResolver, $rendererBridge); - $position = new Position(null, 'homepage.top', null, 0, null, Position::BreakpointTypeMin, []); + $position = new ResponsePosition(null, 'homepage.top', null, 0, null, ResponsePosition::BreakpointTypeMin, []); $rendererBridge ->shouldReceive('renderNotFound') @@ -62,7 +64,7 @@ public function testSingleTemplateShouldBeRendered(): void $renderer = new Renderer($bannersResolver, $rendererBridge); $banner = new Banner('1234', 'Main', 0, null, null, null, []); - $position = new Position('1234', 'homepage.top', 'Homepage top', 0, Position::DisplayTypeSingle, Position::BreakpointTypeMin, [$banner]); + $position = new ResponsePosition('1234', 'homepage.top', 'Homepage top', 0, ResponsePosition::DisplayTypeSingle, ResponsePosition::BreakpointTypeMin, [$banner]); $bannersResolver ->shouldReceive('resolveSingle') @@ -86,7 +88,7 @@ public function testRandomTemplateShouldBeRendered(): void $renderer = new Renderer($bannersResolver, $rendererBridge); $banner = new Banner('1234', 'Main', 0, null, null, null, []); - $position = new Position('1234', 'homepage.top', 'Homepage top', 0, Position::DisplayTypeRandom, Position::BreakpointTypeMin, [$banner]); + $position = new ResponsePosition('1234', 'homepage.top', 'Homepage top', 0, ResponsePosition::DisplayTypeRandom, ResponsePosition::BreakpointTypeMin, [$banner]); $bannersResolver ->shouldReceive('resolveRandom') @@ -113,7 +115,7 @@ public function testMultipleTemplateShouldBeRendered(): void new Banner('1234', 'Main', 0, null, null, null, []), new Banner('1235', 'Secondary', 0, null, null, null, []), ]; - $position = new Position('1234', 'homepage.top', 'Homepage top', 0, Position::DisplayTypeMultiple, Position::BreakpointTypeMin, $banners); + $position = new ResponsePosition('1234', 'homepage.top', 'Homepage top', 0, ResponsePosition::DisplayTypeMultiple, ResponsePosition::BreakpointTypeMin, $banners); $bannersResolver ->shouldReceive('resolveMultiple') @@ -130,13 +132,32 @@ public function testMultipleTemplateShouldBeRendered(): void Assert::same('multiple', $renderer->render($position)); } - public function testRendererExceptionShouldBeThrownWhenBridgeThrowsTheException(): void + public function testClientSideTemplateShouldBeRendered(): void { $bannersResolver = Mockery::mock(BannersResolverInterface::class); $rendererBridge = Mockery::mock(RendererBridgeInterface::class); $renderer = new Renderer($bannersResolver, $rendererBridge); - $position = new Position(null, 'homepage.top', null, 0, null, Position::BreakpointTypeMin, []); + $position = new RequestPosition('homepage.top', [ + new BannerResource('role', 'vip'), + ]); + + $rendererBridge + ->shouldReceive('renderClientSide') + ->once() + ->with($position, []) + ->andReturn('client-side'); + + Assert::same('client-side', $renderer->renderClientSide($position)); + } + + public function testRendererExceptionShouldBeThrownOnRenderingWhenBridgeThrowsTheException(): void + { + $bannersResolver = Mockery::mock(BannersResolverInterface::class); + $rendererBridge = Mockery::mock(RendererBridgeInterface::class); + $renderer = new Renderer($bannersResolver, $rendererBridge); + + $position = new ResponsePosition(null, 'homepage.top', null, 0, null, ResponsePosition::BreakpointTypeMin, []); $rendererBridge ->shouldReceive('renderNotFound') @@ -151,13 +172,34 @@ public function testRendererExceptionShouldBeThrownWhenBridgeThrowsTheException( ); } - public function testRendererExceptionShouldBeThrownWhenBridgeThrowsAnyException(): void + public function testRendererExceptionShouldBeThrownOnClientSideRenderingWhenBridgeThrowsTheException(): void + { + $bannersResolver = Mockery::mock(BannersResolverInterface::class); + $rendererBridge = Mockery::mock(RendererBridgeInterface::class); + $renderer = new Renderer($bannersResolver, $rendererBridge); + + $position = new RequestPosition('homepage.top'); + + $rendererBridge + ->shouldReceive('renderClientSide') + ->once() + ->with($position, []) + ->andThrow(new RendererException('Test exception')); + + Assert::exception( + static fn () => $renderer->renderClientSide($position), + RendererException::class, + 'Test exception', + ); + } + + public function testRendererExceptionShouldBeThrownOnRenderingWhenBridgeThrowsAnyException(): void { $bannersResolver = Mockery::mock(BannersResolverInterface::class); $rendererBridge = Mockery::mock(RendererBridgeInterface::class); $renderer = new Renderer($bannersResolver, $rendererBridge); - $position = new Position(null, 'homepage.top', null, 0, null, Position::BreakpointTypeMin, []); + $position = new ResponsePosition(null, 'homepage.top', null, 0, null, ResponsePosition::BreakpointTypeMin, []); $rendererBridge ->shouldReceive('renderNotFound') @@ -172,6 +214,27 @@ public function testRendererExceptionShouldBeThrownWhenBridgeThrowsAnyException( ); } + public function testRendererExceptionShouldBeThrownOnClientSideRenderingWhenBridgeThrowsAnyException(): void + { + $bannersResolver = Mockery::mock(BannersResolverInterface::class); + $rendererBridge = Mockery::mock(RendererBridgeInterface::class); + $renderer = new Renderer($bannersResolver, $rendererBridge); + + $position = new RequestPosition('homepage.top'); + + $rendererBridge + ->shouldReceive('renderClientSide') + ->once() + ->with($position, []) + ->andThrow(new Exception('Test exception')); + + Assert::exception( + static fn () => $renderer->renderClientSide($position), + RendererException::class, + 'Renderer bridge of type %A% thrown an exception while rendering a position homepage.top: Test exception', + ); + } + protected function tearDown(): void { Mockery::close(); diff --git a/tests/Renderer/TemplatesTest.php b/tests/Renderer/TemplatesTest.php index d549e42..ec1c4cb 100644 --- a/tests/Renderer/TemplatesTest.php +++ b/tests/Renderer/TemplatesTest.php @@ -17,11 +17,11 @@ final class TemplatesTest extends TestCase public function testExceptionShouldBeThrownWhenTemplateFileNotFound(): void { $templates = new Templates([ - Templates::TemplateSingle => __DIR__ . '/path/to/missing-file.phtml', + Templates::Single => __DIR__ . '/path/to/missing-file.phtml', ]); Assert::exception( - static fn () => $templates->getTemplateFile(Templates::TemplateSingle), + static fn () => $templates->getTemplateFile(Templates::Single), RendererException::class, 'Template file "%A%/path/to/missing-file.phtml" not found.', ); @@ -30,11 +30,11 @@ public function testExceptionShouldBeThrownWhenTemplateFileNotFound(): void public function testExceptionShouldBeThrownWhenTemplateFileNotDefined(): void { $templates = new Templates([ - Templates::TemplateSingle => __DIR__ . '/path/to/missing-file.phtml', + Templates::Single => __DIR__ . '/path/to/missing-file.phtml', ]); Assert::exception( - static fn () => $templates->getTemplateFile(Templates::TemplateMultiple), + static fn () => $templates->getTemplateFile(Templates::Multiple), RendererException::class, 'Template file of type "multiple" not defined.', ); @@ -44,10 +44,10 @@ public function testTemplateFileShouldBeReturned(): void { $filename = realpath(__DIR__ . '/../resources/renderer/not-found/templates/not-found1.phtml'); $templates = new Templates([ - Templates::TemplateNotFound => $filename, + Templates::NotFound => $filename, ]); - Assert::same($filename, $templates->getTemplateFile(Templates::TemplateNotFound)); + Assert::same($filename, $templates->getTemplateFile(Templates::NotFound)); } public function testTemplatesShouldBeOverridden(): void @@ -58,21 +58,21 @@ public function testTemplatesShouldBeOverridden(): void $notFoundOverridden = realpath(__DIR__ . '/../resources/renderer/not-found/templates/not-found2.phtml'); $templates = new Templates([ - Templates::TemplateNotFound => $notFound, - Templates::TemplateSingle => $single, + Templates::NotFound => $notFound, + Templates::Single => $single, ]); $overriddenTemplates = $templates->override(new Templates([ - Templates::TemplateNotFound => $notFoundOverridden, + Templates::NotFound => $notFoundOverridden, ])); Assert::notSame($templates, $overriddenTemplates); - Assert::same($notFound, $templates->getTemplateFile(Templates::TemplateNotFound)); - Assert::same($single, $templates->getTemplateFile(Templates::TemplateSingle)); + Assert::same($notFound, $templates->getTemplateFile(Templates::NotFound)); + Assert::same($single, $templates->getTemplateFile(Templates::Single)); - Assert::same($notFoundOverridden, $overriddenTemplates->getTemplateFile(Templates::TemplateNotFound)); - Assert::same($single, $overriddenTemplates->getTemplateFile(Templates::TemplateSingle)); + Assert::same($notFoundOverridden, $overriddenTemplates->getTemplateFile(Templates::NotFound)); + Assert::same($single, $overriddenTemplates->getTemplateFile(Templates::Single)); } } diff --git a/tests/resources/renderer/client-side/data-provider.php b/tests/resources/renderer/client-side/data-provider.php new file mode 100644 index 0000000..4f792d3 --- /dev/null +++ b/tests/resources/renderer/client-side/data-provider.php @@ -0,0 +1,45 @@ + [ + new Position('homepage.top'), + [], + __DIR__ . '/positionOnly.html', + ], + 'With resources' => [ + new Position('homepage.top', [ + new BannerResource('role', 'vip'), + new BannerResource('category', [123, 456]), + ]), + [], + __DIR__ . '/withResources.html', + ], + 'With attributes' => [ + new Position('homepage.top'), + [ + 'class' => 'custom-class', + 'data-custom' => true, + 'data-custom2' => false, + 'data-custom3' => null, + ], + __DIR__ . '/withAttributes.html', + ], + 'With resources and attributes' => [ + new Position('homepage.top', [ + new BannerResource('role', 'vip'), + new BannerResource('category', [123, 456]), + ]), + [ + 'class' => 'custom-class', + 'data-custom' => true, + 'data-custom2' => false, + 'data-custom3' => null, + ], + __DIR__ . '/withResourcesAndAttributes.html', + ], +]; diff --git a/tests/resources/renderer/client-side/positionOnly.html b/tests/resources/renderer/client-side/positionOnly.html new file mode 100644 index 0000000..b5a6f0b --- /dev/null +++ b/tests/resources/renderer/client-side/positionOnly.html @@ -0,0 +1 @@ +
diff --git a/tests/resources/renderer/client-side/templates/client-side1.phtml b/tests/resources/renderer/client-side/templates/client-side1.phtml new file mode 100644 index 0000000..07e3bda --- /dev/null +++ b/tests/resources/renderer/client-side/templates/client-side1.phtml @@ -0,0 +1,10 @@ + +Client-side position "getCode()) ?>". diff --git a/tests/resources/renderer/client-side/withAttributes.html b/tests/resources/renderer/client-side/withAttributes.html new file mode 100644 index 0000000..3febf56 --- /dev/null +++ b/tests/resources/renderer/client-side/withAttributes.html @@ -0,0 +1 @@ +
diff --git a/tests/resources/renderer/client-side/withResources.html b/tests/resources/renderer/client-side/withResources.html new file mode 100644 index 0000000..074d5ad --- /dev/null +++ b/tests/resources/renderer/client-side/withResources.html @@ -0,0 +1 @@ +
diff --git a/tests/resources/renderer/client-side/withResourcesAndAttributes.html b/tests/resources/renderer/client-side/withResourcesAndAttributes.html new file mode 100644 index 0000000..dfb9490 --- /dev/null +++ b/tests/resources/renderer/client-side/withResourcesAndAttributes.html @@ -0,0 +1 @@ +