diff --git a/composer.json b/composer.json index 75899a7..5fd1304 100644 --- a/composer.json +++ b/composer.json @@ -21,6 +21,7 @@ "kubawerlos/php-cs-fixer-custom-fixers": "^3.14", "latte/latte": "^2.11", "mockery/mockery": "^1.6", + "nette/application": "^3.0.8", "nette/bootstrap": "^3.1", "nette/caching": "^3.1", "nette/di": "^3.0", diff --git a/src/Bridge/Nette/DI/AmpClientExtension.php b/src/Bridge/Nette/DI/AmpClientExtension.php index be5be3d..764a2af 100644 --- a/src/Bridge/Nette/DI/AmpClientExtension.php +++ b/src/Bridge/Nette/DI/AmpClientExtension.php @@ -4,33 +4,56 @@ namespace SixtyEightPublishers\AmpClient\Bridge\Nette\DI; +use Closure; +use Nette\Bridges\ApplicationDI\LatteExtension; +use Nette\Bridges\ApplicationLatte\LatteFactory; use Nette\DI\CompilerExtension; +use Nette\DI\Definitions\Reference; use Nette\DI\Definitions\ServiceDefinition; use Nette\DI\Definitions\Statement; use Nette\Schema\Expect; use Nette\Schema\Schema; +use RuntimeException; use SixtyEightPublishers\AmpClient\AmpClient; use SixtyEightPublishers\AmpClient\AmpClientInterface; use SixtyEightPublishers\AmpClient\Bridge\Nette\DI\Config\AmpClientConfig; use SixtyEightPublishers\AmpClient\Bridge\Nette\DI\Config\CacheConfig; use SixtyEightPublishers\AmpClient\Bridge\Nette\DI\Config\HttpConfig; +use SixtyEightPublishers\AmpClient\Bridge\Nette\DI\Config\RendererConfig; use SixtyEightPublishers\AmpClient\Bridge\Nette\NetteCacheStorage; use SixtyEightPublishers\AmpClient\ClientConfig; use SixtyEightPublishers\AmpClient\Http\Cache\CacheStorageInterface; use SixtyEightPublishers\AmpClient\Http\Cache\NoCacheStorage; use SixtyEightPublishers\AmpClient\Http\HttpClientFactory; use SixtyEightPublishers\AmpClient\Http\HttpClientFactoryInterface; +use SixtyEightPublishers\AmpClient\Renderer\BannersResolver; +use SixtyEightPublishers\AmpClient\Renderer\BannersResolverInterface; +use SixtyEightPublishers\AmpClient\Renderer\Latte\ClosureLatteFactory; +use SixtyEightPublishers\AmpClient\Renderer\Latte\LatteRendererBridge; +use SixtyEightPublishers\AmpClient\Renderer\Phtml\PhtmlRendererBridge; +use SixtyEightPublishers\AmpClient\Renderer\Renderer; +use SixtyEightPublishers\AmpClient\Renderer\RendererBridgeInterface; +use SixtyEightPublishers\AmpClient\Renderer\RendererInterface; +use SixtyEightPublishers\AmpClient\Renderer\Templates; use SixtyEightPublishers\AmpClient\Request\ValueObject\BannerResource; use SixtyEightPublishers\AmpClient\Response\Hydrator\BannersResponseHydratorHandler; use SixtyEightPublishers\AmpClient\Response\Hydrator\ResponseHydrator; use SixtyEightPublishers\AmpClient\Response\Hydrator\ResponseHydratorHandlerInterface; use SixtyEightPublishers\AmpClient\Response\Hydrator\ResponseHydratorInterface; +use function array_filter; use function array_values; use function assert; use function count; +use function is_string; +use function sprintf; final class AmpClientExtension extends CompilerExtension { + private const RendererBridges = [ + 'phtml' => PhtmlRendererBridge::class, + 'latte' => LatteRendererBridge::class, + ]; + public function getConfigSchema(): Schema { return Expect::structure([ @@ -67,6 +90,15 @@ public function getConfigSchema(): Schema 'http' => Expect::structure([ 'guzzle_config' => Expect::array(), ])->castTo(HttpConfig::class), + 'renderer' => Expect::structure([ + 'bridge' => Expect::anyOf(Expect::string(), Expect::type(Statement::class)), + 'templates' => Expect::structure([ + 'single' => Expect::string(), + 'random' => Expect::string(), + 'multiple' => Expect::string(), + 'not_found' => Expect::string(), + ])->castTo('array'), + ])->castTo(RendererConfig::class), ])->castTo(AmpClientConfig::class); } @@ -106,16 +138,33 @@ public function loadConfiguration(): void ->setAutowired(false) ->setType(HttpClientFactoryInterface::class) ->setFactory(HttpClientFactory::class, [ - 'responseHydrator' => $this->prefix('@responseHydrator'), + 'responseHydrator' => new Reference($this->prefix('responseHydrator')), 'guzzleClientConfig' => $config->http->guzzle_config, ]); $builder->addDefinition($this->prefix('ampClient')) ->setType(AmpClientInterface::class) ->setFactory(AmpClient::class, [ - 'config' => $this->prefix('@config'), - 'httpClientFactory' => $this->prefix('@httpClientFactory'), - 'cacheStorage' => $this->prefix('@cacheStorage'), + 'config' => new Reference($this->prefix('config')), + 'httpClientFactory' => new Reference($this->prefix('httpClientFactory')), + 'cacheStorage' => new Reference($this->prefix('cacheStorage')), + ]); + + $builder->addDefinition($this->prefix('renderer.rendererBridge')) + ->setAutowired(false) + ->setType(RendererBridgeInterface::class) + ->setFactory($this->resolveRendererBridgeCreator($config->renderer)); + + $builder->addDefinition($this->prefix('renderer.bannersResolver')) + ->setAutowired(false) + ->setType(BannersResolverInterface::class) + ->setFactory(BannersResolver::class); + + $builder->addDefinition($this->prefix('renderer')) + ->setType(RendererInterface::class) + ->setFactory(Renderer::class, [ + 'bannersResolver' => new Reference($this->prefix('renderer.bannersResolver')), + 'rendererBridge' => new Reference($this->prefix('renderer.rendererBridge')), ]); } @@ -187,4 +236,60 @@ private function createClientConfigCreator(AmpClientConfig $config): Statement return $clientConfigFactory; } + + private function resolveRendererBridgeCreator(RendererConfig $config): Statement + { + $rendererBridge = $config->bridge ?? ($this->extensionExists(LatteExtension::class) ? 'latte' : 'phtml'); + + if (is_string($rendererBridge) && isset(self::RendererBridges[$rendererBridge])) { + $rendererBridge = self::RendererBridges[$rendererBridge]; + } + + if (LatteRendererBridge::class === $rendererBridge) { + if (!$this->extensionExists(LatteExtension::class)) { + throw new RuntimeException(sprintf( + 'Renderer of type %s can not be used without the compiler extension of type %s.', + LatteRendererBridge::class, + LatteExtension::class, + )); + } + + $rendererBridge = new Statement(LatteRendererBridge::class, [ + 'latteFactory' => new Statement(ClosureLatteFactory::class, [ + 'factory' => new Statement([Closure::class, 'fromCallable'], [ + [new Reference(LatteFactory::class), 'create'], + ]), + ]), + ]); + } + + if (!($rendererBridge instanceof Statement)) { + $rendererBridge = new Statement($rendererBridge); + } + + $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, + ]); + + if (0 < count($templatesOverride)) { + $rendererBridge = new Statement([$rendererBridge, 'overrideTemplates'], [ + 'templates' => new Statement(Templates::class, [ + 'filesMap' => $templatesOverride, + ]), + ]); + } + + return $rendererBridge; + } + + /** + * @param class-string $classname + */ + private function extensionExists(string $classname): bool + { + return 0 < count($this->compiler->getExtensions($classname)); + } } diff --git a/src/Bridge/Nette/DI/Config/AmpClientConfig.php b/src/Bridge/Nette/DI/Config/AmpClientConfig.php index 573cbac..1c14b3a 100644 --- a/src/Bridge/Nette/DI/Config/AmpClientConfig.php +++ b/src/Bridge/Nette/DI/Config/AmpClientConfig.php @@ -26,4 +26,6 @@ final class AmpClientConfig public CacheConfig $cache; public HttpConfig $http; + + public RendererConfig $renderer; } diff --git a/src/Bridge/Nette/DI/Config/RendererConfig.php b/src/Bridge/Nette/DI/Config/RendererConfig.php new file mode 100644 index 0000000..b0fe56c --- /dev/null +++ b/src/Bridge/Nette/DI/Config/RendererConfig.php @@ -0,0 +1,16 @@ + */ + public array $templates = []; +} diff --git a/src/Exception/RendererException.php b/src/Exception/RendererException.php index def79b8..31d3182 100644 --- a/src/Exception/RendererException.php +++ b/src/Exception/RendererException.php @@ -41,6 +41,16 @@ public static function rendererBridgeThrownError(string $rendererBridgeClassname ); } + public static function templateFileNotDefined(string $type): self + { + return new self( + sprintf( + 'Template file of type "%s" not defined.', + $type, + ), + ); + } + public static function templateFileNotFound(string $filename): self { return new self( diff --git a/src/Renderer/Latte/ClosureLatteFactory.php b/src/Renderer/Latte/ClosureLatteFactory.php new file mode 100644 index 0000000..1802a49 --- /dev/null +++ b/src/Renderer/Latte/ClosureLatteFactory.php @@ -0,0 +1,32 @@ +factory = $factory; + } + + public function create(): Engine + { + $latte = ($this->factory)(); + + assert($latte instanceof Engine); + + return $latte; + } +} diff --git a/src/Renderer/Latte/LatteFactoryInterface.php b/src/Renderer/Latte/LatteFactoryInterface.php new file mode 100644 index 0000000..28e579f --- /dev/null +++ b/src/Renderer/Latte/LatteFactoryInterface.php @@ -0,0 +1,12 @@ +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', + ]); + } + + public static function fromEngine(Engine $engine): self + { + return new self( + new ClosureLatteFactory(static fn (): Engine => $engine), + ); + } + + public function overrideTemplates(Templates $templates): self + { + $renderer = clone $this; + $renderer->templates = $this->templates->override($templates); + + return $renderer; + } + + public function renderNotFound(Position $position): string + { + return $this->getLatte()->renderToString( + $this->templates->getTemplateFile(Templates::TemplateNotFound), + new NotFoundTemplate($position), + ); + } + + public function renderSingle(Position $position, ?Banner $banner): string + { + return $this->getLatte()->renderToString( + $this->templates->getTemplateFile(Templates::TemplateSingle), + new SingleTemplate($position, $banner), + ); + } + + public function renderRandom(Position $position, ?Banner $banner): string + { + return $this->getLatte()->renderToString( + $this->templates->getTemplateFile(Templates::TemplateRandom), + new RandomTemplate($position, $banner), + ); + } + + public function renderMultiple(Position $position, array $banners): string + { + return $this->getLatte()->renderToString( + $this->templates->getTemplateFile(Templates::TemplateMultiple), + new MultipleTemplate($position, $banners), + ); + } + + private function getLatte(): Engine + { + if (null === $this->latte) { + $this->latte = $this->latteFactory->create(); + } + + return $this->latte; + } +} diff --git a/src/Renderer/Latte/Templates/MultipleTemplate.php b/src/Renderer/Latte/Templates/MultipleTemplate.php new file mode 100644 index 0000000..5c54cbb --- /dev/null +++ b/src/Renderer/Latte/Templates/MultipleTemplate.php @@ -0,0 +1,27 @@ + */ + public array $banners; + + /** + * @param array $banners + */ + public function __construct( + Position $position, + array $banners + ) { + $this->position = $position; + $this->banners = $banners; + } +} diff --git a/src/Renderer/Latte/Templates/NotFoundTemplate.php b/src/Renderer/Latte/Templates/NotFoundTemplate.php new file mode 100644 index 0000000..9c5c16d --- /dev/null +++ b/src/Renderer/Latte/Templates/NotFoundTemplate.php @@ -0,0 +1,18 @@ +position = $position; + } +} diff --git a/src/Renderer/Latte/Templates/RandomTemplate.php b/src/Renderer/Latte/Templates/RandomTemplate.php new file mode 100644 index 0000000..80d9c0a --- /dev/null +++ b/src/Renderer/Latte/Templates/RandomTemplate.php @@ -0,0 +1,23 @@ +position = $position; + $this->banner = $banner; + } +} diff --git a/src/Renderer/Latte/Templates/SingleTemplate.php b/src/Renderer/Latte/Templates/SingleTemplate.php new file mode 100644 index 0000000..ef2e864 --- /dev/null +++ b/src/Renderer/Latte/Templates/SingleTemplate.php @@ -0,0 +1,23 @@ +position = $position; + $this->banner = $banner; + } +} diff --git a/src/Renderer/Latte/Templates/contents.fragment.latte b/src/Renderer/Latte/Templates/contents.fragment.latte new file mode 100644 index 0000000..05bd344 --- /dev/null +++ b/src/Renderer/Latte/Templates/contents.fragment.latte @@ -0,0 +1,35 @@ +{varType SixtyEightPublishers\AmpClient\Request\ValueObject\Position $position} +{varType SixtyEightPublishers\AmpClient\Response\ValueObject\Banner|null $banner} + +{foreach $banner->getContents() as $content} + {if $content instanceof SixtyEightPublishers\AmpClient\Response\ValueObject\ImageContent} + {varType SixtyEightPublishers\AmpClient\Response\ValueObject\ImageContent $content} + + + + + + {$content->getAlt()} + + + {elseif $content instanceof SixtyEightPublishers\AmpClient\Response\ValueObject\HtmlContent} + {varType SixtyEightPublishers\AmpClient\Response\ValueObject\HtmlContent $content} + +
+ {$content->getHtml()|noescape} +
+ {/if} +{/foreach} + +{= new SixtyEightPublishers\AmpClient\Renderer\BreakpointStyle\BreakpointStyle($position, $banner)|noescape} diff --git a/src/Renderer/Latte/Templates/multiple.latte b/src/Renderer/Latte/Templates/multiple.latte new file mode 100644 index 0000000..77e4571 --- /dev/null +++ b/src/Renderer/Latte/Templates/multiple.latte @@ -0,0 +1,15 @@ +{templateType SixtyEightPublishers\AmpClient\Bridge\Latte\Templates\MultipleTemplate} + +
+ {do $banners = array_filter($banners, fn(SixtyEightPublishers\AmpClient\Response\ValueObject\Banner $banner) => 0 < count($banner->getContents()))} +
+
+
+ {include 'contents.fragment.latte', position: $position, banner: $banner} +
+
+
+
diff --git a/src/Renderer/Latte/Templates/notFound.latte b/src/Renderer/Latte/Templates/notFound.latte new file mode 100644 index 0000000..cc581e2 --- /dev/null +++ b/src/Renderer/Latte/Templates/notFound.latte @@ -0,0 +1,3 @@ +{templateType SixtyEightPublishers\AmpClient\Bridge\Latte\Templates\NotFoundTemplate} + +
diff --git a/src/Renderer/Latte/Templates/random.latte b/src/Renderer/Latte/Templates/random.latte new file mode 100644 index 0000000..4048111 --- /dev/null +++ b/src/Renderer/Latte/Templates/random.latte @@ -0,0 +1,10 @@ +{templateType SixtyEightPublishers\AmpClient\Bridge\Latte\Templates\RandomTemplate} + +
+
+ {include 'contents.fragment.latte', position: $position, banner: $banner} +
+
diff --git a/src/Renderer/Latte/Templates/single.latte b/src/Renderer/Latte/Templates/single.latte new file mode 100644 index 0000000..a4d2cc8 --- /dev/null +++ b/src/Renderer/Latte/Templates/single.latte @@ -0,0 +1,10 @@ +{templateType SixtyEightPublishers\AmpClient\Bridge\Latte\Templates\SingleTemplate} + +
+
+ {include 'contents.fragment.latte', position: $position, banner: $banner} +
+
diff --git a/src/Renderer/Phtml/PhtmlRendererBridge.php b/src/Renderer/Phtml/PhtmlRendererBridge.php index 7ee0b0b..84c4cf9 100644 --- a/src/Renderer/Phtml/PhtmlRendererBridge.php +++ b/src/Renderer/Phtml/PhtmlRendererBridge.php @@ -4,51 +4,33 @@ namespace SixtyEightPublishers\AmpClient\Renderer\Phtml; -use SixtyEightPublishers\AmpClient\Exception\RendererException; use SixtyEightPublishers\AmpClient\Renderer\OutputBuffer; use SixtyEightPublishers\AmpClient\Renderer\RendererBridgeInterface; +use SixtyEightPublishers\AmpClient\Renderer\Templates; use SixtyEightPublishers\AmpClient\Response\ValueObject\Banner; use SixtyEightPublishers\AmpClient\Response\ValueObject\Position; use Throwable; -use function array_merge; -use function file_exists; final class PhtmlRendererBridge implements RendererBridgeInterface { - public const TemplateSingle = 'single'; - public const TemplateRandom = 'random'; - public const TemplateMultiple = 'multiple'; - public const TemplateNotFound = 'not-found'; + private Templates $templates; - /** - * @var array{ - * single: string, - * random: string, - * multiple: string, - * 'not-found': string, - * } - */ - private array $templates; + 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', + ]); + } - /** - * @param array{ - * single?: string, - * random?: string, - * multiple?: string, - * not-found?: string, - * } $templatesOverrides - */ - public function __construct(array $templatesOverrides = []) + public function overrideTemplates(Templates $templates): self { - $this->templates = array_merge( - [ - self::TemplateSingle => __DIR__ . '/Templates/single.phtml', - self::TemplateRandom => __DIR__ . '/Templates/random.phtml', - self::TemplateMultiple => __DIR__ . '/Templates/multiple.phtml', - self::TemplateNotFound => __DIR__ . '/Templates/not-found.phtml', - ], - $templatesOverrides, - ); + $renderer = clone $this; + $renderer->templates = $this->templates->override($templates); + + return $renderer; } /** @@ -56,7 +38,7 @@ public function __construct(array $templatesOverrides = []) */ public function renderNotFound(Position $position): string { - $filename = $this->getTemplateFilename(self::TemplateNotFound); + $filename = $this->templates->getTemplateFile(Templates::TemplateNotFound); return OutputBuffer::capture(function () use ($filename, $position) { require $filename; @@ -68,7 +50,7 @@ public function renderNotFound(Position $position): string */ public function renderSingle(Position $position, ?Banner $banner): string { - $filename = $this->getTemplateFilename(self::TemplateSingle); + $filename = $this->templates->getTemplateFile(Templates::TemplateSingle); return OutputBuffer::capture(function () use ($filename, $position, $banner) { require $filename; @@ -80,7 +62,7 @@ public function renderSingle(Position $position, ?Banner $banner): string */ public function renderRandom(Position $position, ?Banner $banner): string { - $filename = $this->getTemplateFilename(self::TemplateRandom); + $filename = $this->templates->getTemplateFile(Templates::TemplateRandom); return OutputBuffer::capture(function () use ($filename, $position, $banner) { require $filename; @@ -92,24 +74,10 @@ public function renderRandom(Position $position, ?Banner $banner): string */ public function renderMultiple(Position $position, array $banners): string { - $filename = $this->getTemplateFilename(self::TemplateMultiple); + $filename = $this->templates->getTemplateFile(Templates::TemplateMultiple); return OutputBuffer::capture(function () use ($filename, $position, $banners) { require $filename; }); } - - /** - * @throws RendererException - */ - private function getTemplateFilename(string $type): string - { - $filename = $this->templates[$type] ?? ''; - - if (!file_exists($filename)) { - throw RendererException::templateFileNotFound($filename); - } - - return $filename; - } } diff --git a/src/Renderer/Phtml/Templates/not-found.phtml b/src/Renderer/Phtml/Templates/notFound.phtml similarity index 100% rename from src/Renderer/Phtml/Templates/not-found.phtml rename to src/Renderer/Phtml/Templates/notFound.phtml diff --git a/src/Renderer/RendererBridgeInterface.php b/src/Renderer/RendererBridgeInterface.php index d9446b3..268b72d 100644 --- a/src/Renderer/RendererBridgeInterface.php +++ b/src/Renderer/RendererBridgeInterface.php @@ -9,6 +9,8 @@ interface RendererBridgeInterface { + public function overrideTemplates(Templates $templates): self; + public function renderNotFound(Position $position): string; public function renderSingle(Position $position, ?Banner $banner): string; diff --git a/src/Renderer/Templates.php b/src/Renderer/Templates.php new file mode 100644 index 0000000..b40f489 --- /dev/null +++ b/src/Renderer/Templates.php @@ -0,0 +1,67 @@ +filesMap = $filesMap; + } + + /** + * @throws RendererException + */ + public function getTemplateFile(string $type): string + { + $filename = $this->filesMap[$type] ?? null; + + if (null === $filename) { + throw RendererException::templateFileNotDefined($type); + } + + if (!file_exists($filename)) { + throw RendererException::templateFileNotFound($filename); + } + + return $filename; + } + + public function override(self $templates): self + { + return new self( + array_merge( + $this->filesMap, + $templates->filesMap, + ), + ); + } +} diff --git a/tests/Bridge/Nette/DI/AmpClientExtensionTest.php b/tests/Bridge/Nette/DI/AmpClientExtensionTest.php index 880b014..0303ac0 100644 --- a/tests/Bridge/Nette/DI/AmpClientExtensionTest.php +++ b/tests/Bridge/Nette/DI/AmpClientExtensionTest.php @@ -6,6 +6,9 @@ use Closure; use Nette\Caching\Storages\MemoryStorage; +use Nette\Configurator; +use Nette\DI\Container; +use RuntimeException; use SixtyEightPublishers\AmpClient\AmpClient; use SixtyEightPublishers\AmpClient\AmpClientInterface; use SixtyEightPublishers\AmpClient\Bridge\Nette\NetteCacheStorage; @@ -13,11 +16,18 @@ use SixtyEightPublishers\AmpClient\Http\Cache\NoCacheStorage; use SixtyEightPublishers\AmpClient\Http\HttpClientFactory; use SixtyEightPublishers\AmpClient\Http\HttpClientFactoryInterface; +use SixtyEightPublishers\AmpClient\Renderer\Latte\LatteRendererBridge; +use SixtyEightPublishers\AmpClient\Renderer\Phtml\PhtmlRendererBridge; +use SixtyEightPublishers\AmpClient\Renderer\Renderer; +use SixtyEightPublishers\AmpClient\Renderer\RendererBridgeInterface; +use SixtyEightPublishers\AmpClient\Renderer\RendererInterface; +use SixtyEightPublishers\AmpClient\Renderer\Templates; use SixtyEightPublishers\AmpClient\Request\ValueObject\BannerResource; use SixtyEightPublishers\AmpClient\Response\Hydrator\BannersResponseHydratorHandler; use SixtyEightPublishers\AmpClient\Response\Hydrator\ResponseHydrator; use Tester\Assert; use Tester\TestCase; +use function assert; use function call_user_func; require __DIR__ . '/../../../bootstrap.php'; @@ -26,7 +36,12 @@ final class AmpClientExtensionTest extends TestCase { public function testContainerWithMinimalConfiguration(): void { - $client = $this->getClientFromContainer(__DIR__ . '/config.minimal.neon'); + $container = ContainerFactory::create(__DIR__ . '/config.minimal.neon', function (Configurator $configurator): void { + # make sure the LatteExtension is still between the default extensions. + Assert::hasKey('latte', $configurator->defaultExtensions); + }); + + $client = $this->getClientFromContainer($container); $httpClientFactory = $this->unwrapHttpClientFactory($client); Assert::equal(ClientConfig::create('https://www.example.com', 'test'), $client->getConfig()); @@ -41,11 +56,107 @@ public function testContainerWithMinimalConfiguration(): void ), $httpClientFactory, ); + + $renderer = $this->getRendererFromContainer($container); + $rendererBridge = $this->unwrapRendererBridge($renderer); + + Assert::type(LatteRendererBridge::class, $rendererBridge); + } + + public function testContainerWithoutLatteExtension(): void + { + $container = ContainerFactory::create(__DIR__ . '/config.minimal.neon', function (Configurator $configurator): void { + unset($configurator->defaultExtensions['latte']); + }); + + $renderer = $this->getRendererFromContainer($container); + $rendererBridge = $this->unwrapRendererBridge($renderer); + + Assert::type(PhtmlRendererBridge::class, $rendererBridge); + } + + public function testContainerWithPhtmlRenderer(): void + { + $container = ContainerFactory::create(__DIR__ . '/config.withPhtmlRenderer.neon'); + + $renderer = $this->getRendererFromContainer($container); + $rendererBridge = $this->unwrapRendererBridge($renderer); + + Assert::type(PhtmlRendererBridge::class, $rendererBridge); + } + + public function testContainerWithPhtmlRendererAsStatement(): void + { + $container = ContainerFactory::create(__DIR__ . '/config.withPhtmlRendererAsStatement.neon'); + + $renderer = $this->getRendererFromContainer($container); + $rendererBridge = $this->unwrapRendererBridge($renderer); + + Assert::type(PhtmlRendererBridge::class, $rendererBridge); + } + + public function testContainerWithLatteRenderer(): void + { + $container = ContainerFactory::create(__DIR__ . '/config.withLatteRenderer.neon'); + + $renderer = $this->getRendererFromContainer($container); + $rendererBridge = $this->unwrapRendererBridge($renderer); + + Assert::type(LatteRendererBridge::class, $rendererBridge); + } + + public function testContainerWithLatteRendererAsStatement(): void + { + $container = ContainerFactory::create(__DIR__ . '/config.withLatteRendererAsStatement.neon'); + + $renderer = $this->getRendererFromContainer($container); + $rendererBridge = $this->unwrapRendererBridge($renderer); + + Assert::type(LatteRendererBridge::class, $rendererBridge); + } + + public function testContainerWithLatteRendererAsStatementAndMissingLatteExtension(): void + { + $container = ContainerFactory::create(__DIR__ . '/config.withLatteRendererAsStatement.neon', function (Configurator $configurator): void { + unset($configurator->defaultExtensions['latte']); + }); + + $renderer = $this->getRendererFromContainer($container); + $rendererBridge = $this->unwrapRendererBridge($renderer); + + Assert::type(LatteRendererBridge::class, $rendererBridge); + } + + public function testExceptionShouldBeThrownWhenLatteRendererIsExplicitlyConfiguredButLatteExtensionMissing(): void + { + Assert::exception( + static fn () => ContainerFactory::create(__DIR__ . '/config.withLatteRenderer.neon', function (Configurator $configurator): void { + unset($configurator->defaultExtensions['latte']); + }), + RuntimeException::class, + 'Renderer of type %A%\LatteRendererBridge can not be used without the compiler extension of type Nette\Bridges\ApplicationDI\LatteExtension.', + ); + } + + public function testContainerWithRendererTemplates(): void + { + $container = ContainerFactory::create(__DIR__ . '/config.withRendererTemplates.neon'); + + $renderer = $this->getRendererFromContainer($container); + $rendererBridge = $this->unwrapRendererBridge($renderer); + $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)); } public function testContainerWithMethodOption(): void { - $client = $this->getClientFromContainer(__DIR__ . '/config.withMethod.neon'); + $container = ContainerFactory::create(__DIR__ . '/config.withMethod.neon'); + $client = $this->getClientFromContainer($container); Assert::equal( ClientConfig::create('https://www.example.com', 'test') @@ -56,7 +167,8 @@ public function testContainerWithMethodOption(): void public function testContainerWithVersionOption(): void { - $client = $this->getClientFromContainer(__DIR__ . '/config.withVersion.neon'); + $container = ContainerFactory::create(__DIR__ . '/config.withVersion.neon'); + $client = $this->getClientFromContainer($container); Assert::equal( ClientConfig::create('https://www.example.com', 'test') @@ -67,7 +179,8 @@ public function testContainerWithVersionOption(): void public function testContainerWithLocaleOption(): void { - $client = $this->getClientFromContainer(__DIR__ . '/config.withLocale.neon'); + $container = ContainerFactory::create(__DIR__ . '/config.withLocale.neon'); + $client = $this->getClientFromContainer($container); Assert::equal( ClientConfig::create('https://www.example.com', 'test') @@ -78,7 +191,8 @@ public function testContainerWithLocaleOption(): void public function testContainerWithDefaultResourcesOption(): void { - $client = $this->getClientFromContainer(__DIR__ . '/config.withDefaultResources.neon'); + $container = ContainerFactory::create(__DIR__ . '/config.withDefaultResources.neon'); + $client = $this->getClientFromContainer($container); Assert::equal( ClientConfig::create('https://www.example.com', 'test') @@ -92,7 +206,8 @@ public function testContainerWithDefaultResourcesOption(): void public function testContainerWithOriginOption(): void { - $client = $this->getClientFromContainer(__DIR__ . '/config.withOrigin.neon'); + $container = ContainerFactory::create(__DIR__ . '/config.withOrigin.neon'); + $client = $this->getClientFromContainer($container); Assert::equal( ClientConfig::create('https://www.example.com', 'test') @@ -103,7 +218,8 @@ public function testContainerWithOriginOption(): void public function testContainerWithCacheOptions(): void { - $client = $this->getClientFromContainer(__DIR__ . '/config.withCache.neon'); + $container = ContainerFactory::create(__DIR__ . '/config.withCache.neon'); + $client = $this->getClientFromContainer($container); Assert::equal( ClientConfig::create('https://www.example.com', 'test') @@ -122,7 +238,8 @@ public function testContainerWithCacheOptions(): void public function testContainerWithGuzzleConfig(): void { - $client = $this->getClientFromContainer(__DIR__ . '/config.withGuzzleConfig.neon'); + $container = ContainerFactory::create(__DIR__ . '/config.withGuzzleConfig.neon'); + $client = $this->getClientFromContainer($container); $httpClientFactory = $this->unwrapHttpClientFactory($client); Assert::type(HttpClientFactory::class, $httpClientFactory); @@ -136,9 +253,8 @@ public function testContainerWithGuzzleConfig(): void ], $guzzleClientConfig); } - private function getClientFromContainer(string $configFile): AmpClient + private function getClientFromContainer(Container $container): AmpClient { - $container = ContainerFactory::create($configFile); $client = $container->getByType(AmpClientInterface::class); Assert::type(AmpClient::class, $client); @@ -146,12 +262,28 @@ private function getClientFromContainer(string $configFile): AmpClient return $client; } + private function getRendererFromContainer(Container $container): Renderer + { + $renderer = $container->getByType(RendererInterface::class); + + Assert::type(Renderer::class, $renderer); + + return $renderer; + } + private function unwrapHttpClientFactory(AmpClient $ampClient): HttpClientFactoryInterface { return call_user_func(Closure::bind(static function () use ($ampClient): HttpClientFactoryInterface { return $ampClient->httpClientFactory; }, null, AmpClient::class)); } + + private function unwrapRendererBridge(Renderer $renderer): RendererBridgeInterface + { + return call_user_func(Closure::bind(static function () use ($renderer): RendererBridgeInterface { + return $renderer->rendererBridge; + }, null, Renderer::class)); + } } (new AmpClientExtensionTest())->run(); diff --git a/tests/Bridge/Nette/DI/ContainerFactory.php b/tests/Bridge/Nette/DI/ContainerFactory.php index 25fba88..0622bd4 100644 --- a/tests/Bridge/Nette/DI/ContainerFactory.php +++ b/tests/Bridge/Nette/DI/ContainerFactory.php @@ -4,6 +4,7 @@ namespace SixtyEightPublishers\AmpClient\Tests\Bridge\Nette\DI; +use Closure; use Nette\Bootstrap\Configurator; use Nette\DI\Container; use Tester\Helpers; @@ -17,7 +18,7 @@ private function __construct() {} /** * @param string|array $configFiles */ - public static function create($configFiles, bool $debug = false): Container + public static function create($configFiles, ?Closure $beforeContainerCreated = null): Container { $tempDir = sys_get_temp_dir() . '/' . uniqid('68publishers:AmpClientPhp', true); @@ -25,12 +26,19 @@ public static function create($configFiles, bool $debug = false): Container $configurator = new Configurator(); $configurator->setTempDirectory($tempDir); - $configurator->setDebugMode($debug); + $configurator->setDebugMode(false); + $configurator->addStaticParameters([ + 'resources' => __DIR__ . '/../../../resources', + ]); foreach ((array) $configFiles as $configFile) { $configurator->addConfig($configFile); } + if (null !== $beforeContainerCreated) { + $beforeContainerCreated($configurator); + } + return $configurator->createContainer(); } } diff --git a/tests/Bridge/Nette/DI/config.withLatteRenderer.neon b/tests/Bridge/Nette/DI/config.withLatteRenderer.neon new file mode 100644 index 0000000..e47e847 --- /dev/null +++ b/tests/Bridge/Nette/DI/config.withLatteRenderer.neon @@ -0,0 +1,8 @@ +extensions: + amp_client: SixtyEightPublishers\AmpClient\Bridge\Nette\DI\AmpClientExtension + +amp_client: + url: https://www.example.com + channel: test + renderer: + bridge: latte diff --git a/tests/Bridge/Nette/DI/config.withLatteRendererAsStatement.neon b/tests/Bridge/Nette/DI/config.withLatteRendererAsStatement.neon new file mode 100644 index 0000000..583d4e5 --- /dev/null +++ b/tests/Bridge/Nette/DI/config.withLatteRendererAsStatement.neon @@ -0,0 +1,8 @@ +extensions: + amp_client: SixtyEightPublishers\AmpClient\Bridge\Nette\DI\AmpClientExtension + +amp_client: + url: https://www.example.com + channel: test + renderer: + bridge: SixtyEightPublishers\AmpClient\Renderer\Latte\LatteRendererBridge::fromEngine(Latte\Engine()) diff --git a/tests/Bridge/Nette/DI/config.withPhtmlRenderer.neon b/tests/Bridge/Nette/DI/config.withPhtmlRenderer.neon new file mode 100644 index 0000000..2f782e2 --- /dev/null +++ b/tests/Bridge/Nette/DI/config.withPhtmlRenderer.neon @@ -0,0 +1,8 @@ +extensions: + amp_client: SixtyEightPublishers\AmpClient\Bridge\Nette\DI\AmpClientExtension + +amp_client: + url: https://www.example.com + channel: test + renderer: + bridge: phtml diff --git a/tests/Bridge/Nette/DI/config.withPhtmlRendererAsStatement.neon b/tests/Bridge/Nette/DI/config.withPhtmlRendererAsStatement.neon new file mode 100644 index 0000000..1d498bb --- /dev/null +++ b/tests/Bridge/Nette/DI/config.withPhtmlRendererAsStatement.neon @@ -0,0 +1,8 @@ +extensions: + amp_client: SixtyEightPublishers\AmpClient\Bridge\Nette\DI\AmpClientExtension + +amp_client: + url: https://www.example.com + channel: test + renderer: + bridge: SixtyEightPublishers\AmpClient\Renderer\Phtml\PhtmlRendererBridge() diff --git a/tests/Bridge/Nette/DI/config.withRendererTemplates.neon b/tests/Bridge/Nette/DI/config.withRendererTemplates.neon new file mode 100644 index 0000000..c58b89e --- /dev/null +++ b/tests/Bridge/Nette/DI/config.withRendererTemplates.neon @@ -0,0 +1,13 @@ +extensions: + amp_client: SixtyEightPublishers\AmpClient\Bridge\Nette\DI\AmpClientExtension + +amp_client: + url: https://www.example.com + channel: test + renderer: + bridge: phtml + templates: + single: %resources%/renderer/single/templates/single1.phtml + random: %resources%/renderer/random/templates/random1.phtml + multiple: %resources%/renderer/multiple/templates/multiple1.phtml + not_found: %resources%/renderer/not-found/templates/not-found1.phtml diff --git a/tests/Renderer/Latte/ClosureLatteFactoryTest.php b/tests/Renderer/Latte/ClosureLatteFactoryTest.php new file mode 100644 index 0000000..238d5f5 --- /dev/null +++ b/tests/Renderer/Latte/ClosureLatteFactoryTest.php @@ -0,0 +1,33 @@ +create()); + Assert::same($latte, $factory->create()); + Assert::same(2, $counter); + } +} + +(new ClosureLatteFactoryTest())->run(); diff --git a/tests/Renderer/Latte/LatteRendererBridgeTest.php b/tests/Renderer/Latte/LatteRendererBridgeTest.php new file mode 100644 index 0000000..77f831d --- /dev/null +++ b/tests/Renderer/Latte/LatteRendererBridgeTest.php @@ -0,0 +1,113 @@ +createRendererBridge(); + $modifiedRenderer = $renderer->overrideTemplates(new Templates([ + Templates::TemplateSingle => '/path/to/file', + ])); + + $originalTemplates = call_user_func(Closure::bind(static fn () => $renderer->templates, null, LatteRendererBridge::class)); + $overriddenTemplates = call_user_func(Closure::bind(static fn () => $modifiedRenderer->templates, null, LatteRendererBridge::class)); + + Assert::notSame($renderer, $modifiedRenderer); + Assert::notSame($originalTemplates, $overriddenTemplates); + } + + /** + * @dataProvider notFoundTemplateDataProvider + */ + public function testNotFoundTemplateRendering( + Position $position, + string $expectationFile + ): void { + $renderer = $this->createRendererBridge(); + + AssertHtml::assert($expectationFile, $renderer->renderNotFound($position)); + } + + /** + * @dataProvider singleTemplateDataProvider + */ + public function testSingleTemplateRendering( + Position $position, + ?Banner $banner, + string $expectationFile + ): void { + $renderer = $this->createRendererBridge(); + + AssertHtml::assert($expectationFile, $renderer->renderSingle($position, $banner)); + } + + /** + * @dataProvider randomTemplateDataProvider + */ + public function testRandomTemplateRendering( + Position $position, + ?Banner $banner, + string $expectationFile + ): void { + $renderer = $this->createRendererBridge(); + + AssertHtml::assert($expectationFile, $renderer->renderRandom($position, $banner)); + } + + /** + * @dataProvider multipleTemplateDataProvider + * */ + public function testMultipleTemplateRendering( + Position $position, + array $banners, + string $expectationFile + ): void { + $renderer = $this->createRendererBridge(); + + AssertHtml::assert($expectationFile, $renderer->renderMultiple($position, $banners)); + } + + public function notFoundTemplateDataProvider(): array + { + return require __DIR__ . '/../../resources/renderer/not-found/data-provider.php'; + } + + public function singleTemplateDataProvider(): array + { + return require __DIR__ . '/../../resources/renderer/single/data-provider.php'; + } + + public function randomTemplateDataProvider(): array + { + return require __DIR__ . '/../../resources/renderer/random/data-provider.php'; + } + + public function multipleTemplateDataProvider(): array + { + return require __DIR__ . '/../../resources/renderer/multiple/data-provider.php'; + } + + private function createRendererBridge(): LatteRendererBridge + { + return LatteRendererBridge::fromEngine(new Engine()); + } +} + +(new LatteRendererBridgeTest())->run(); diff --git a/tests/Renderer/Phtml/PhtmlRendererBridgeTest.php b/tests/Renderer/Phtml/PhtmlRendererBridgeTest.php index b7cc385..11a3035 100644 --- a/tests/Renderer/Phtml/PhtmlRendererBridgeTest.php +++ b/tests/Renderer/Phtml/PhtmlRendererBridgeTest.php @@ -5,8 +5,8 @@ namespace SixtyEightPublishers\AmpClient\Tests\Renderer\Phtml; use Closure; -use SixtyEightPublishers\AmpClient\Exception\RendererException; use SixtyEightPublishers\AmpClient\Renderer\Phtml\PhtmlRendererBridge; +use SixtyEightPublishers\AmpClient\Renderer\Templates; use SixtyEightPublishers\AmpClient\Response\ValueObject\Banner; use SixtyEightPublishers\AmpClient\Response\ValueObject\Position; use SixtyEightPublishers\AmpClient\Tests\Renderer\AssertHtml; @@ -18,68 +18,18 @@ final class PhtmlRendererBridgeTest extends TestCase { - public function testTemplatesShouldBeOverwritten(): void + public function testTemplatesShouldBeOverridden(): void { - $renderer = new PhtmlRendererBridge([ - PhtmlRendererBridge::TemplateSingle => '/path/to/template.phtml', - ]); - - $templates = call_user_func(Closure::bind(static fn () => $renderer->templates, null, PhtmlRendererBridge::class)); + $renderer = new PhtmlRendererBridge(); + $modifiedRenderer = $renderer->overrideTemplates(new Templates([ + Templates::TemplateSingle => '/path/to/file', + ])); - Assert::same('/path/to/template.phtml', $templates[PhtmlRendererBridge::TemplateSingle]); - } + $originalTemplates = call_user_func(Closure::bind(static fn () => $renderer->templates, null, PhtmlRendererBridge::class)); + $overriddenTemplates = call_user_func(Closure::bind(static fn () => $modifiedRenderer->templates, null, PhtmlRendererBridge::class)); - public function testRendererExceptionShouldBeThrownWhenTemplateFileNotExists(): void - { - $renderer = new PhtmlRendererBridge([ - PhtmlRendererBridge::TemplateSingle => 'non-existent.single.phtml', - PhtmlRendererBridge::TemplateMultiple => 'non-existent.multiple.phtml', - PhtmlRendererBridge::TemplateRandom => 'non-existent.random.phtml', - PhtmlRendererBridge::TemplateNotFound => 'non-existent.not-found.phtml', - ]); - - Assert::exception( - static function () use ($renderer): void { - $banner = new Banner('1234', 'Main', 0, null, null, null, []); - $position = new Position('1234', 'homepage.top', 'Homepage top', 0, Position::DisplayTypeSingle, Position::BreakpointTypeMin, [$banner]); - - $renderer->renderSingle($position, $banner); - }, - RendererException::class, - 'Template file "non-existent.single.phtml" not found.', - ); - - Assert::exception( - static function () use ($renderer): void { - $banner = new Banner('1234', 'Main', 0, null, null, null, []); - $position = new Position('1234', 'homepage.top', 'Homepage top', 0, Position::DisplayTypeMultiple, Position::BreakpointTypeMin, [$banner]); - - $renderer->renderMultiple($position, [$banner]); - }, - RendererException::class, - 'Template file "non-existent.multiple.phtml" not found.', - ); - - Assert::exception( - static function () use ($renderer): void { - $banner = new Banner('1234', 'Main', 0, null, null, null, []); - $position = new Position('1234', 'homepage.top', 'Homepage top', 0, Position::DisplayTypeRandom, Position::BreakpointTypeMin, [$banner]); - - $renderer->renderRandom($position, $banner); - }, - RendererException::class, - 'Template file "non-existent.random.phtml" not found.', - ); - - Assert::exception( - static function () use ($renderer): void { - $position = new Position(null, 'homepage.top', null, 0, null, Position::BreakpointTypeMin, []); - - $renderer->renderNotFound($position); - }, - RendererException::class, - 'Template file "non-existent.not-found.phtml" not found.', - ); + Assert::notSame($renderer, $modifiedRenderer); + Assert::notSame($originalTemplates, $overriddenTemplates); } /** diff --git a/tests/Renderer/TemplatesTest.php b/tests/Renderer/TemplatesTest.php new file mode 100644 index 0000000..d549e42 --- /dev/null +++ b/tests/Renderer/TemplatesTest.php @@ -0,0 +1,79 @@ + __DIR__ . '/path/to/missing-file.phtml', + ]); + + Assert::exception( + static fn () => $templates->getTemplateFile(Templates::TemplateSingle), + RendererException::class, + 'Template file "%A%/path/to/missing-file.phtml" not found.', + ); + } + + public function testExceptionShouldBeThrownWhenTemplateFileNotDefined(): void + { + $templates = new Templates([ + Templates::TemplateSingle => __DIR__ . '/path/to/missing-file.phtml', + ]); + + Assert::exception( + static fn () => $templates->getTemplateFile(Templates::TemplateMultiple), + RendererException::class, + 'Template file of type "multiple" not defined.', + ); + } + + public function testTemplateFileShouldBeReturned(): void + { + $filename = realpath(__DIR__ . '/../resources/renderer/not-found/templates/not-found1.phtml'); + $templates = new Templates([ + Templates::TemplateNotFound => $filename, + ]); + + Assert::same($filename, $templates->getTemplateFile(Templates::TemplateNotFound)); + } + + public function testTemplatesShouldBeOverridden(): void + { + $notFound = realpath(__DIR__ . '/../resources/renderer/not-found/templates/not-found1.phtml'); + $single = $notFound; # can be same for testing purposes + + $notFoundOverridden = realpath(__DIR__ . '/../resources/renderer/not-found/templates/not-found2.phtml'); + + $templates = new Templates([ + Templates::TemplateNotFound => $notFound, + Templates::TemplateSingle => $single, + ]); + + $overriddenTemplates = $templates->override(new Templates([ + Templates::TemplateNotFound => $notFoundOverridden, + ])); + + Assert::notSame($templates, $overriddenTemplates); + + Assert::same($notFound, $templates->getTemplateFile(Templates::TemplateNotFound)); + Assert::same($single, $templates->getTemplateFile(Templates::TemplateSingle)); + + Assert::same($notFoundOverridden, $overriddenTemplates->getTemplateFile(Templates::TemplateNotFound)); + Assert::same($single, $overriddenTemplates->getTemplateFile(Templates::TemplateSingle)); + } +} + +(new TemplatesTest())->run(); diff --git a/tests/resources/renderer/multiple/templates/multiple1.phtml b/tests/resources/renderer/multiple/templates/multiple1.phtml new file mode 100644 index 0000000..8b906ca --- /dev/null +++ b/tests/resources/renderer/multiple/templates/multiple1.phtml @@ -0,0 +1,10 @@ + +Random position "getCode()) ?>". diff --git a/tests/resources/renderer/not-found/templates/not-found1.phtml b/tests/resources/renderer/not-found/templates/not-found1.phtml new file mode 100644 index 0000000..8b93d86 --- /dev/null +++ b/tests/resources/renderer/not-found/templates/not-found1.phtml @@ -0,0 +1,10 @@ + +Position "getCode()) ?>" not found. diff --git a/tests/resources/renderer/not-found/templates/not-found2.phtml b/tests/resources/renderer/not-found/templates/not-found2.phtml new file mode 100644 index 0000000..768e86e --- /dev/null +++ b/tests/resources/renderer/not-found/templates/not-found2.phtml @@ -0,0 +1,10 @@ + +Position "getCode()) ?>" not found! diff --git a/tests/resources/renderer/random/templates/random1.phtml b/tests/resources/renderer/random/templates/random1.phtml new file mode 100644 index 0000000..23d3971 --- /dev/null +++ b/tests/resources/renderer/random/templates/random1.phtml @@ -0,0 +1,10 @@ + +Multiple position "getCode()) ?>". diff --git a/tests/resources/renderer/single/templates/single1.phtml b/tests/resources/renderer/single/templates/single1.phtml new file mode 100644 index 0000000..d2e01e3 --- /dev/null +++ b/tests/resources/renderer/single/templates/single1.phtml @@ -0,0 +1,10 @@ + +Single position "getCode()) ?>".