From cc9c616c317d1338c42a1acbfd8224be552af7ef Mon Sep 17 00:00:00 2001 From: Gabi Udrescu Date: Sun, 30 Aug 2026 13:13:22 +0000 Subject: [PATCH] feat: support channel-specific GTM configuration --- config/features/channels.yaml | 10 ++ config/features/context.yaml | 1 + config/features/environment.yaml | 1 + config/features/route.yaml | 1 + config/services.yaml | 16 ++ src/DependencyInjection/Configuration.php | 23 +++ src/DependencyInjection/GtmExtension.php | 21 +++ src/EventListener/AddRouteListener.php | 8 +- src/EventListener/ChannelGtmListener.php | 50 ++++++ src/EventListener/ContextListener.php | 8 +- src/EventListener/EnvironmentListener.php | 8 +- src/Resolver/ChannelFeatureResolver.php | 33 ++++ src/Twig/GtmChannelExtension.php | 29 +++ templates/events_javascript.html.twig | 2 + .../packages/stefandoorn_sylius_gtm.yaml | 5 + .../DependencyInjection/GtmExtensionTest.php | 79 ++++++++ .../EventListener/AddRouteListenerTest.php | 20 +++ .../EventListener/ChannelGtmListenerTest.php | 168 ++++++++++++++++++ .../EventListener/ContextListenerTest.php | 26 +++ .../EventListener/EnvironmentListenerTest.php | 21 +++ .../Resolver/ChannelFeatureResolverTest.php | 71 ++++++++ tests/Unit/Twig/GtmChannelExtensionTest.php | 38 ++++ 22 files changed, 636 insertions(+), 3 deletions(-) create mode 100644 config/features/channels.yaml create mode 100644 src/EventListener/ChannelGtmListener.php create mode 100644 src/Resolver/ChannelFeatureResolver.php create mode 100644 src/Twig/GtmChannelExtension.php create mode 100644 tests/Unit/DependencyInjection/GtmExtensionTest.php create mode 100644 tests/Unit/EventListener/ChannelGtmListenerTest.php create mode 100644 tests/Unit/Resolver/ChannelFeatureResolverTest.php create mode 100644 tests/Unit/Twig/GtmChannelExtensionTest.php diff --git a/config/features/channels.yaml b/config/features/channels.yaml new file mode 100644 index 0000000..2c0dcaf --- /dev/null +++ b/config/features/channels.yaml @@ -0,0 +1,10 @@ +services: + + sylius.google_tag_manager.listener.channels: + class: GtmPlugin\EventListener\ChannelGtmListener + arguments: + - "@google_tag_manager" + - "@sylius.context.channel" + - "%gtm.channels%" + tags: + - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest } diff --git a/config/features/context.yaml b/config/features/context.yaml index 39f1904..943b044 100644 --- a/config/features/context.yaml +++ b/config/features/context.yaml @@ -8,5 +8,6 @@ services: - "@sylius.context.channel" - "@sylius.context.locale" - "@sylius.context.currency" + - "@sylius.google_tag_manager.resolver.channel_feature" tags: - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest } diff --git a/config/features/environment.yaml b/config/features/environment.yaml index ecbdb73..0466857 100644 --- a/config/features/environment.yaml +++ b/config/features/environment.yaml @@ -6,5 +6,6 @@ services: - "%gtm.features.environment%" - "@google_tag_manager" - "%kernel.environment%" + - "@sylius.google_tag_manager.resolver.channel_feature" tags: - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest } diff --git a/config/features/route.yaml b/config/features/route.yaml index 97ebc4e..26a99f8 100644 --- a/config/features/route.yaml +++ b/config/features/route.yaml @@ -5,5 +5,6 @@ services: arguments: - "%gtm.features.route%" - "@google_tag_manager" + - "@sylius.google_tag_manager.resolver.channel_feature" tags: - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest } diff --git a/config/services.yaml b/config/services.yaml index 98f31a2..75ec272 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -1,2 +1,18 @@ imports: - { resource: features/*.yaml } + +services: + + sylius.google_tag_manager.resolver.channel_feature: + class: GtmPlugin\Resolver\ChannelFeatureResolver + arguments: + - "@sylius.context.channel" + - "%gtm.features%" + - "%gtm.channels%" + + sylius.google_tag_manager.twig.channel: + class: GtmPlugin\Twig\GtmChannelExtension + arguments: + - "@sylius.google_tag_manager.resolver.channel_feature" + tags: + - { name: twig.extension } diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 5cb24ca..99eec38 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -26,6 +26,29 @@ public function getConfigTreeBuilder(): TreeBuilder ->booleanNode('events')->defaultTrue()->end() ->end() ->end() + ->arrayNode('channels') + ->useAttributeAsKey('code') + ->arrayPrototype() + ->validate() + ->ifTrue(static fn (array $v): bool => ($v['id'] ?? null) === null && + ($v['enabled'] ?? null) === null && + ($v['features'] ?? []) === []) + ->thenInvalid('Channel entry must define at least one of "id", "enabled" or a "features" override.') + ->end() + ->children() + ->scalarNode('id')->defaultNull()->end() + ->booleanNode('enabled')->defaultNull()->end() + ->arrayNode('features') + ->children() + ->booleanNode('environment')->end() + ->booleanNode('route')->end() + ->booleanNode('context')->end() + ->booleanNode('events')->end() + ->end() + ->end() + ->end() + ->end() + ->end() ->end() ; diff --git a/src/DependencyInjection/GtmExtension.php b/src/DependencyInjection/GtmExtension.php index 373425b..bb6eaa5 100644 --- a/src/DependencyInjection/GtmExtension.php +++ b/src/DependencyInjection/GtmExtension.php @@ -24,6 +24,9 @@ public function load(array $configs, ContainerBuilder $container): void $container->setParameter($parameter, $setting); } + $container->setParameter('gtm.features', $config['features']); + $container->setParameter('gtm.channels', $this->normaliseChannels($config['channels'] ?? [])); + $loader->load('services.yaml'); } @@ -32,6 +35,24 @@ public function prepend(ContainerBuilder $container): void $this->prependSyliusTwigHooks($container); } + /** + * @param array}> $channels + * + * @return array}> + */ + private function normaliseChannels(array $channels): array + { + foreach ($channels as $code => $channel) { + $channels[$code] = [ + 'id' => $channel['id'] ?? null, + 'enabled' => $channel['enabled'] ?? (($channel['id'] ?? null) !== null ? true : null), + 'features' => $channel['features'] ?? [], + ]; + } + + return $channels; + } + protected function prependSyliusTwigHooks(ContainerBuilder $container): void { if (!$container->hasExtension('sylius_twig_hooks')) { diff --git a/src/EventListener/AddRouteListener.php b/src/EventListener/AddRouteListener.php index 623d4e6..a3b1ca9 100644 --- a/src/EventListener/AddRouteListener.php +++ b/src/EventListener/AddRouteListener.php @@ -4,6 +4,7 @@ namespace GtmPlugin\EventListener; +use GtmPlugin\Resolver\ChannelFeatureResolver; use Symfony\Component\HttpKernel\Event\RequestEvent; use Xynnn\GoogleTagManagerBundle\Service\GoogleTagManagerInterface; @@ -12,12 +13,17 @@ final class AddRouteListener public function __construct( private readonly bool $enabled, private readonly GoogleTagManagerInterface $googleTagManager, + private readonly ?ChannelFeatureResolver $featureResolver = null, ) { + if ($this->featureResolver === null) { + trigger_error('Not passing a ChannelFeatureResolver to AddRouteListener is deprecated and it will be required in the next major version.', \E_USER_DEPRECATED); + } } public function onKernelRequest(RequestEvent $event): void { - if (!$this->enabled) { + $enabled = $this->featureResolver?->isEnabled('route') ?? $this->enabled; + if (!$enabled) { return; } diff --git a/src/EventListener/ChannelGtmListener.php b/src/EventListener/ChannelGtmListener.php new file mode 100644 index 0000000..b727b5e --- /dev/null +++ b/src/EventListener/ChannelGtmListener.php @@ -0,0 +1,50 @@ + $channels + * Pre-normalised by GtmExtension::load(). + */ + public function __construct( + private readonly GoogleTagManagerInterface $googleTagManager, + private readonly ChannelContextInterface $channelContext, + private readonly array $channels, + ) { + } + + public function onKernelRequest(RequestEvent $event): void + { + if (!$event->isMainRequest()) { + return; + } + + try { + $code = $this->channelContext->getChannel()->getCode(); + } catch (ChannelNotFoundException) { + return; + } + + $config = $this->channels[$code] ?? null; + if ($config === null) { + return; + } + + if ($config['id'] !== null) { + $this->googleTagManager->setId($config['id']); + } + + if ($config['enabled'] !== null) { + $config['enabled'] ? $this->googleTagManager->enable() : $this->googleTagManager->disable(); + } + } +} diff --git a/src/EventListener/ContextListener.php b/src/EventListener/ContextListener.php index 67c29cb..68875fd 100644 --- a/src/EventListener/ContextListener.php +++ b/src/EventListener/ContextListener.php @@ -4,6 +4,7 @@ namespace GtmPlugin\EventListener; +use GtmPlugin\Resolver\ChannelFeatureResolver; use Sylius\Component\Channel\Context\ChannelContextInterface; use Sylius\Component\Channel\Context\ChannelNotFoundException; use Sylius\Component\Currency\Context\CurrencyContextInterface; @@ -19,12 +20,17 @@ public function __construct( private readonly ChannelContextInterface $channelContext, private readonly LocaleContextInterface $localeContext, private readonly CurrencyContextInterface $currencyContext, + private readonly ?ChannelFeatureResolver $featureResolver = null, ) { + if ($this->featureResolver === null) { + trigger_error('Not passing a ChannelFeatureResolver to ContextListener is deprecated and it will be required in the next major version.', \E_USER_DEPRECATED); + } } public function onKernelRequest(RequestEvent $event): void { - if (!$this->enabled) { + $enabled = $this->featureResolver?->isEnabled('context') ?? $this->enabled; + if (!$enabled) { return; } diff --git a/src/EventListener/EnvironmentListener.php b/src/EventListener/EnvironmentListener.php index d66741c..b4dd091 100644 --- a/src/EventListener/EnvironmentListener.php +++ b/src/EventListener/EnvironmentListener.php @@ -4,6 +4,7 @@ namespace GtmPlugin\EventListener; +use GtmPlugin\Resolver\ChannelFeatureResolver; use Symfony\Component\HttpKernel\Event\RequestEvent; use Xynnn\GoogleTagManagerBundle\Service\GoogleTagManagerInterface; @@ -13,12 +14,17 @@ public function __construct( private readonly bool $enabled, private readonly GoogleTagManagerInterface $googleTagManager, private readonly string $environment, + private readonly ?ChannelFeatureResolver $featureResolver = null, ) { + if ($this->featureResolver === null) { + trigger_error('Not passing a ChannelFeatureResolver to EnvironmentListener is deprecated and it will be required in the next major version.', \E_USER_DEPRECATED); + } } public function onKernelRequest(RequestEvent $event): void { - if (!$this->enabled) { + $enabled = $this->featureResolver?->isEnabled('environment') ?? $this->enabled; + if (!$enabled) { return; } diff --git a/src/Resolver/ChannelFeatureResolver.php b/src/Resolver/ChannelFeatureResolver.php new file mode 100644 index 0000000..22698d4 --- /dev/null +++ b/src/Resolver/ChannelFeatureResolver.php @@ -0,0 +1,33 @@ + $globalFeatures + * @param array}> $channels + */ + public function __construct( + private readonly ChannelContextInterface $channelContext, + private readonly array $globalFeatures, + private readonly array $channels, + ) { + } + + public function isEnabled(string $feature): bool + { + try { + $code = $this->channelContext->getChannel()->getCode(); + + return $this->channels[$code]['features'][$feature] ?? $this->globalFeatures[$feature] ?? false; + } catch (ChannelNotFoundException) { + return $this->globalFeatures[$feature] ?? false; + } + } +} diff --git a/src/Twig/GtmChannelExtension.php b/src/Twig/GtmChannelExtension.php new file mode 100644 index 0000000..7172e9a --- /dev/null +++ b/src/Twig/GtmChannelExtension.php @@ -0,0 +1,29 @@ +gtmChannelAllows(...)), + ]; + } + + public function gtmChannelAllows(string $feature): bool + { + return $this->featureResolver->isEnabled($feature); + } +} diff --git a/templates/events_javascript.html.twig b/templates/events_javascript.html.twig index 78fece9..5c717b5 100644 --- a/templates/events_javascript.html.twig +++ b/templates/events_javascript.html.twig @@ -1 +1,3 @@ +{% if gtm_channel_allows('events') %} +{% endif %} diff --git a/tests/Application/config/packages/stefandoorn_sylius_gtm.yaml b/tests/Application/config/packages/stefandoorn_sylius_gtm.yaml index 58b0bd3..6330952 100644 --- a/tests/Application/config/packages/stefandoorn_sylius_gtm.yaml +++ b/tests/Application/config/packages/stefandoorn_sylius_gtm.yaml @@ -5,3 +5,8 @@ gtm: route: true context: true events: true + channels: + FASHION_WEB: + id: "GTM-FASHION" + BUSINESS_WEB: + enabled: false diff --git a/tests/Unit/DependencyInjection/GtmExtensionTest.php b/tests/Unit/DependencyInjection/GtmExtensionTest.php new file mode 100644 index 0000000..6c0867a --- /dev/null +++ b/tests/Unit/DependencyInjection/GtmExtensionTest.php @@ -0,0 +1,79 @@ +buildContainer([]); + + self::assertSame([], $container->getParameter('gtm.channels')); + } + + public function testChannelsAreNormalisedWithDefaults(): void + { + $container = $this->buildContainer([ + 'channels' => [ + 'B2C' => ['id' => 'GTM-B2C'], + 'B2B' => ['enabled' => false], + 'STAFF' => ['id' => 'GTM-STAFF', 'enabled' => false], + 'EVENTS_OFF' => ['features' => ['events' => false]], + ], + ]); + + self::assertSame([ + 'B2C' => ['id' => 'GTM-B2C', 'enabled' => true, 'features' => []], + 'B2B' => ['id' => null, 'enabled' => false, 'features' => []], + 'STAFF' => ['id' => 'GTM-STAFF', 'enabled' => false, 'features' => []], + 'EVENTS_OFF' => ['id' => null, 'enabled' => null, 'features' => ['events' => false]], + ], $container->getParameter('gtm.channels')); + } + + public function testChannelListenerIsRegistered(): void + { + $container = $this->buildContainer([ + 'channels' => ['B2C' => ['id' => 'GTM-B2C']], + ]); + + self::assertTrue($container->hasDefinition('sylius.google_tag_manager.listener.channels')); + $definition = $container->getDefinition('sylius.google_tag_manager.listener.channels'); + self::assertSame('GtmPlugin\\EventListener\\ChannelGtmListener', $definition->getClass()); + self::assertTrue($definition->hasTag('kernel.event_listener')); + } + + public function testFeatureResolverIsRegistered(): void + { + $container = $this->buildContainer([]); + + self::assertTrue($container->hasDefinition('sylius.google_tag_manager.resolver.channel_feature')); + $definition = $container->getDefinition('sylius.google_tag_manager.resolver.channel_feature'); + self::assertSame('GtmPlugin\\Resolver\\ChannelFeatureResolver', $definition->getClass()); + } + + public function testTwigChannelExtensionIsRegistered(): void + { + $container = $this->buildContainer([]); + + self::assertTrue($container->hasDefinition('sylius.google_tag_manager.twig.channel')); + $definition = $container->getDefinition('sylius.google_tag_manager.twig.channel'); + self::assertSame('GtmPlugin\\Twig\\GtmChannelExtension', $definition->getClass()); + self::assertTrue($definition->hasTag('twig.extension')); + } + + /** @param array $config */ + private function buildContainer(array $config): ContainerBuilder + { + $container = new ContainerBuilder(); + $extension = new GtmExtension(); + $extension->load([$config], $container); + + return $container; + } +} diff --git a/tests/Unit/EventListener/AddRouteListenerTest.php b/tests/Unit/EventListener/AddRouteListenerTest.php index 93a33a7..a182962 100644 --- a/tests/Unit/EventListener/AddRouteListenerTest.php +++ b/tests/Unit/EventListener/AddRouteListenerTest.php @@ -5,7 +5,10 @@ namespace Tests\GtmPlugin\Unit\EventListener; use GtmPlugin\EventListener\AddRouteListener; +use GtmPlugin\Resolver\ChannelFeatureResolver; use PHPUnit\Framework\TestCase; +use Sylius\Component\Channel\Context\ChannelContextInterface; +use Sylius\Component\Channel\Context\ChannelNotFoundException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\RequestEvent; use Xynnn\GoogleTagManagerBundle\Service\GoogleTagManager; @@ -17,6 +20,7 @@ public function testAddRouteIsAddedToGtmObject(): void $request = new Request(attributes: ['_route' => 'test_route']); $gtm = new GoogleTagManager(true, 'id1234'); + $this->expectUserDeprecationMessage('Not passing a ChannelFeatureResolver to AddRouteListener is deprecated and it will be required in the next major version.'); $listener = new AddRouteListener(true, $gtm); $mock = $this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock(); @@ -38,4 +42,20 @@ public function testAddRouteIsAddedToGtmObject(): void self::assertArrayHasKey('route', $gtm->getData()); self::assertSame($gtm->getData()['route'], 'test_route'); } + + public function testResolverDecisionOverridesBoolWhenResolverProvided(): void + { + $channelContext = $this->createMock(ChannelContextInterface::class); + $channelContext->method('getChannel')->willThrowException(new ChannelNotFoundException()); + $resolver = new ChannelFeatureResolver($channelContext, ['route' => false], []); + + $gtm = new GoogleTagManager(true, 'id1234'); + $listener = new AddRouteListener(true, $gtm, $resolver); + + $mock = $this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock(); + $mock->method('isMainRequest')->willReturn(true); + $listener->onKernelRequest($mock); + + self::assertArrayNotHasKey('route', $gtm->getData()); + } } diff --git a/tests/Unit/EventListener/ChannelGtmListenerTest.php b/tests/Unit/EventListener/ChannelGtmListenerTest.php new file mode 100644 index 0000000..55ef44c --- /dev/null +++ b/tests/Unit/EventListener/ChannelGtmListenerTest.php @@ -0,0 +1,168 @@ +setCode('B2C'); + + $channelContext = $this->createMock(ChannelContextInterface::class); + $channelContext->method('getChannel')->willReturn($channel); + + $gtm = new GoogleTagManager(true, 'GTM-GLOBAL'); + + $listener = new ChannelGtmListener( + $gtm, + $channelContext, + [ + 'B2C' => ['id' => 'GTM-B2C', 'enabled' => true, 'features' => []], + ], + ); + + $event = $this->createMock(RequestEvent::class); + $event->method('isMainRequest')->willReturn(true); + + $listener->onKernelRequest($event); + + self::assertSame('GTM-B2C', $gtm->getId()); + self::assertTrue($gtm->isEnabled()); + } + + public function testListedChannelWithEnabledFalseDisablesService(): void + { + $channel = new Channel(); + $channel->setCode('B2B'); + + $channelContext = $this->createMock(ChannelContextInterface::class); + $channelContext->method('getChannel')->willReturn($channel); + + $gtm = new GoogleTagManager(true, 'GTM-GLOBAL'); + + $listener = new ChannelGtmListener( + $gtm, + $channelContext, + [ + 'B2B' => ['id' => null, 'enabled' => false, 'features' => []], + ], + ); + + $event = $this->createMock(RequestEvent::class); + $event->method('isMainRequest')->willReturn(true); + + $listener->onKernelRequest($event); + + self::assertFalse($gtm->isEnabled()); + self::assertSame('GTM-GLOBAL', $gtm->getId()); + } + + public function testFeatureOnlyChannelLeavesGlobalGtmStateUntouched(): void + { + $channel = new Channel(); + $channel->setCode('B2C'); + + $channelContext = $this->createMock(ChannelContextInterface::class); + $channelContext->method('getChannel')->willReturn($channel); + + $gtm = new GoogleTagManager(true, 'GTM-GLOBAL'); + $listener = new ChannelGtmListener( + $gtm, + $channelContext, + ['B2C' => ['id' => null, 'enabled' => null, 'features' => ['events' => false]]], + ); + + $event = $this->createMock(RequestEvent::class); + $event->method('isMainRequest')->willReturn(true); + $listener->onKernelRequest($event); + + self::assertTrue($gtm->isEnabled()); + self::assertSame('GTM-GLOBAL', $gtm->getId()); + } + + public function testUnlistedChannelLeavesServiceUntouched(): void + { + $channel = new Channel(); + $channel->setCode('STAFF'); + + $channelContext = $this->createMock(ChannelContextInterface::class); + $channelContext->method('getChannel')->willReturn($channel); + + $gtm = new GoogleTagManager(true, 'GTM-GLOBAL'); + + $listener = new ChannelGtmListener( + $gtm, + $channelContext, + [ + 'B2C' => ['id' => 'GTM-B2C', 'enabled' => true, 'features' => []], + ], + ); + + $event = $this->createMock(RequestEvent::class); + $event->method('isMainRequest')->willReturn(true); + + $listener->onKernelRequest($event); + + self::assertTrue($gtm->isEnabled()); + self::assertSame('GTM-GLOBAL', $gtm->getId()); + } + + public function testSubRequestIsIgnored(): void + { + $channelContext = $this->createMock(ChannelContextInterface::class); + $channelContext->expects(self::never())->method('getChannel'); + + $gtm = new GoogleTagManager(true, 'GTM-GLOBAL'); + + $listener = new ChannelGtmListener( + $gtm, + $channelContext, + [ + 'B2C' => ['id' => 'GTM-B2C', 'enabled' => true, 'features' => []], + ], + ); + + $event = $this->createMock(RequestEvent::class); + $event->method('isMainRequest')->willReturn(false); + + $listener->onKernelRequest($event); + + self::assertTrue($gtm->isEnabled()); + self::assertSame('GTM-GLOBAL', $gtm->getId()); + } + + public function testChannelNotFoundLeavesServiceUntouched(): void + { + $channelContext = $this->createMock(ChannelContextInterface::class); + $channelContext->method('getChannel')->willThrowException(new ChannelNotFoundException()); + + $gtm = new GoogleTagManager(true, 'GTM-GLOBAL'); + + $listener = new ChannelGtmListener( + $gtm, + $channelContext, + [ + 'B2C' => ['id' => 'GTM-B2C', 'enabled' => true, 'features' => []], + ], + ); + + $event = $this->createMock(RequestEvent::class); + $event->method('isMainRequest')->willReturn(true); + + $listener->onKernelRequest($event); + + self::assertTrue($gtm->isEnabled()); + self::assertSame('GTM-GLOBAL', $gtm->getId()); + } +} diff --git a/tests/Unit/EventListener/ContextListenerTest.php b/tests/Unit/EventListener/ContextListenerTest.php index 34f92f3..a4762e8 100644 --- a/tests/Unit/EventListener/ContextListenerTest.php +++ b/tests/Unit/EventListener/ContextListenerTest.php @@ -5,8 +5,10 @@ namespace Tests\GtmPlugin\Unit\EventListener; use GtmPlugin\EventListener\ContextListener; +use GtmPlugin\Resolver\ChannelFeatureResolver; use PHPUnit\Framework\TestCase; use Sylius\Component\Channel\Context\ChannelContextInterface; +use Sylius\Component\Channel\Context\ChannelNotFoundException; use Sylius\Component\Core\Model\Channel; use Sylius\Component\Currency\Context\CurrencyContextInterface; use Sylius\Component\Locale\Context\LocaleContextInterface; @@ -30,6 +32,7 @@ public function testEnvironmentIsAddedToGtmObject(): void $channelContext->expects($this->once())->method('getChannel')->willReturn($channel); $gtm = new GoogleTagManager(true, 'id1234'); + $this->expectUserDeprecationMessage('Not passing a ChannelFeatureResolver to ContextListener is deprecated and it will be required in the next major version.'); $listener = new ContextListener( true, $gtm, @@ -53,4 +56,27 @@ public function testEnvironmentIsAddedToGtmObject(): void self::assertSame($gtm->getData()['channel']['code'], 'channelCode'); self::assertSame($gtm->getData()['channel']['name'], 'channelName'); } + + public function testResolverDecisionOverridesBoolWhenResolverProvided(): void + { + $channelContext = $this->createMock(ChannelContextInterface::class); + $channelContext->method('getChannel')->willThrowException(new ChannelNotFoundException()); + $resolver = new ChannelFeatureResolver($channelContext, ['context' => false], []); + + $gtm = new GoogleTagManager(true, 'id1234'); + $listener = new ContextListener( + true, + $gtm, + $this->createMock(ChannelContextInterface::class), + $this->createMock(LocaleContextInterface::class), + $this->createMock(CurrencyContextInterface::class), + $resolver, + ); + + $mock = $this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock(); + $mock->method('isMainRequest')->willReturn(true); + $listener->onKernelRequest($mock); + + self::assertSame([], $gtm->getData()); + } } diff --git a/tests/Unit/EventListener/EnvironmentListenerTest.php b/tests/Unit/EventListener/EnvironmentListenerTest.php index ff30c0c..87389b3 100644 --- a/tests/Unit/EventListener/EnvironmentListenerTest.php +++ b/tests/Unit/EventListener/EnvironmentListenerTest.php @@ -5,7 +5,10 @@ namespace Tests\GtmPlugin\Unit\EventListener; use GtmPlugin\EventListener\EnvironmentListener; +use GtmPlugin\Resolver\ChannelFeatureResolver; use PHPUnit\Framework\TestCase; +use Sylius\Component\Channel\Context\ChannelContextInterface; +use Sylius\Component\Channel\Context\ChannelNotFoundException; use Symfony\Component\HttpKernel\Event\RequestEvent; use Xynnn\GoogleTagManagerBundle\Service\GoogleTagManager; @@ -14,6 +17,7 @@ final class EnvironmentListenerTest extends TestCase public function testEnvironmentIsAddedToGtmObject(): void { $gtm = new GoogleTagManager(true, 'id1234'); + $this->expectUserDeprecationMessage('Not passing a ChannelFeatureResolver to EnvironmentListener is deprecated and it will be required in the next major version.'); $listener = new EnvironmentListener(true, $gtm, 'test_env'); $mock = $this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock(); $mock->method('isMainRequest')->willReturn(true); @@ -22,4 +26,21 @@ public function testEnvironmentIsAddedToGtmObject(): void self::assertArrayHasKey('env', $gtm->getData()); self::assertSame($gtm->getData()['env'], 'test_env'); } + + public function testResolverDecisionOverridesBoolWhenResolverProvided(): void + { + // bool says ON, resolver says OFF (globally). Resolver wins. + $channelContext = $this->createMock(ChannelContextInterface::class); + $channelContext->method('getChannel')->willThrowException(new ChannelNotFoundException()); + $resolver = new ChannelFeatureResolver($channelContext, ['environment' => false], []); + + $gtm = new GoogleTagManager(true, 'id1234'); + $listener = new EnvironmentListener(true, $gtm, 'test_env', $resolver); + + $mock = $this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock(); + $mock->method('isMainRequest')->willReturn(true); + $listener->onKernelRequest($mock); + + self::assertArrayNotHasKey('env', $gtm->getData()); + } } diff --git a/tests/Unit/Resolver/ChannelFeatureResolverTest.php b/tests/Unit/Resolver/ChannelFeatureResolverTest.php new file mode 100644 index 0000000..cb68a76 --- /dev/null +++ b/tests/Unit/Resolver/ChannelFeatureResolverTest.php @@ -0,0 +1,71 @@ +channelContext('STAFF'), + ['events' => true], + ['B2C' => ['id' => 'GTM-B2C', 'enabled' => true, 'features' => ['events' => false]]], + ); + + self::assertTrue($resolver->isEnabled('events')); + } + + public function testReturnsChannelOverrideWhenSet(): void + { + $resolver = new ChannelFeatureResolver( + $this->channelContext('B2B'), + ['events' => true], + ['B2B' => ['id' => null, 'enabled' => true, 'features' => ['events' => false]]], + ); + + self::assertFalse($resolver->isEnabled('events')); + } + + public function testFallsBackToGlobalWhenChannelListedButFeatureNotOverridden(): void + { + $resolver = new ChannelFeatureResolver( + $this->channelContext('B2C'), + ['route' => true, 'events' => false], + ['B2C' => ['id' => 'GTM-B2C', 'enabled' => true, 'features' => ['events' => true]]], + ); + + self::assertTrue($resolver->isEnabled('route')); + } + + public function testFallsBackToGlobalWhenChannelCannotBeResolved(): void + { + $context = $this->createMock(ChannelContextInterface::class); + $context->method('getChannel')->willThrowException(new ChannelNotFoundException()); + + $resolver = new ChannelFeatureResolver( + $context, + ['context' => true], + ['B2C' => ['id' => 'GTM-B2C', 'enabled' => true, 'features' => ['context' => false]]], + ); + + self::assertTrue($resolver->isEnabled('context')); + } + + private function channelContext(string $code): ChannelContextInterface + { + $channel = new Channel(); + $channel->setCode($code); + $context = $this->createMock(ChannelContextInterface::class); + $context->method('getChannel')->willReturn($channel); + + return $context; + } +} diff --git a/tests/Unit/Twig/GtmChannelExtensionTest.php b/tests/Unit/Twig/GtmChannelExtensionTest.php new file mode 100644 index 0000000..8cebdc1 --- /dev/null +++ b/tests/Unit/Twig/GtmChannelExtensionTest.php @@ -0,0 +1,38 @@ +resolver(true)); + $names = array_map(static fn (TwigFunction $f): string => $f->getName(), $extension->getFunctions()); + + self::assertContains('gtm_channel_allows', $names); + } + + public function testFunctionDelegatesToResolver(): void + { + $extension = new GtmChannelExtension($this->resolver(false)); + + self::assertFalse($extension->gtmChannelAllows('events')); + } + + private function resolver(bool $enabled): ChannelFeatureResolver + { + $context = $this->createMock(ChannelContextInterface::class); + $context->method('getChannel')->willThrowException(new ChannelNotFoundException()); + + return new ChannelFeatureResolver($context, ['events' => $enabled], []); + } +}