diff --git a/README.md b/README.md index 842e26b0..0034f2c7 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ Below is a rundown of all configuration options `www.example.com/example-category?color=red`. If Seo path slugs is selected the url is constructed as `www.example.com/example-category/color/red`. +8) Use category URL from Tweakwise: When enabled, category facet links use the URL provided by Tweakwise in the navigation response instead of URLs generated by Magento. This reduces server-side computation and makes category URLs more predictable. Only applies on category pages, not on search results. Falls back to the standard Magento-generated URL when Tweakwise does not provide a link for an item. #### Seo (All settings depend on Enabled having value yes) 1) Enabled: use Seo options yes or no. diff --git a/src/Block/Catalog/Product/ProductList/Toolbar/Plugin.php b/src/Block/Catalog/Product/ProductList/Toolbar/Plugin.php index f36e0c5c..697ac5fe 100644 --- a/src/Block/Catalog/Product/ProductList/Toolbar/Plugin.php +++ b/src/Block/Catalog/Product/ProductList/Toolbar/Plugin.php @@ -87,7 +87,7 @@ public function aroundGetAvailableOrders(Toolbar $subject, Closure $proceed) } /** @var SortFieldType[] $sortFields */ - $sortFields = $this->context->getResponse()->getProperties()->getSortFields(); // @phpstan-ignore-line + $sortFields = $this->context->getResponse()->getProperties()->getSortFields() ?? []; // @phpstan-ignore-line $result = []; foreach ($sortFields as $field) { diff --git a/src/Block/LayeredNavigation/RenderLayered/DefaultRenderer.php b/src/Block/LayeredNavigation/RenderLayered/DefaultRenderer.php index 51a21424..ad20e126 100644 --- a/src/Block/LayeredNavigation/RenderLayered/DefaultRenderer.php +++ b/src/Block/LayeredNavigation/RenderLayered/DefaultRenderer.php @@ -108,8 +108,13 @@ public function getCategoryUrl(Item $item): string { $catUrl = $item->getUrl(); - if (strpos($catUrl, $this->getBaseUrl()) === false) { - $catUrl = $this->getBaseUrl() . $item->getUrl(); + // Compare scheme-agnostically to handle http vs https mismatches between + // the Tweakwise-provided link and the Magento base URL. + $catUrlWithoutScheme = preg_replace('#^https?://#', '', $catUrl); + $baseUrlWithoutScheme = preg_replace('#^https?://#', '', $this->getBaseUrl()); + + if (strpos($catUrlWithoutScheme, $baseUrlWithoutScheme) === false) { + $catUrl = rtrim($this->getBaseUrl(), '/') . '/' . ltrim($catUrl, '/'); } return $catUrl; diff --git a/src/Model/Catalog/Layer/Filter.php b/src/Model/Catalog/Layer/Filter.php index 16173b07..453b5e40 100644 --- a/src/Model/Catalog/Layer/Filter.php +++ b/src/Model/Catalog/Layer/Filter.php @@ -356,7 +356,6 @@ protected function createItem(AttributeType $attributeType) $item = $this->itemFactory->create(['filter' => $this, 'attributeType' => $attributeType]); $children = []; - // @phpstan-ignore-next-line foreach ($attributeType->getChildren() as $childAttributeType) { $children[] = $this->createItem($childAttributeType); } diff --git a/src/Model/Catalog/Layer/NavigationContext/CurrentContext.php b/src/Model/Catalog/Layer/NavigationContext/CurrentContext.php index 0434d29e..fe0abb8a 100644 --- a/src/Model/Catalog/Layer/NavigationContext/CurrentContext.php +++ b/src/Model/Catalog/Layer/NavigationContext/CurrentContext.php @@ -71,7 +71,7 @@ public function getResponse() */ public function getTweakwiseRequestId(): string { - $headers = $this->getContext()->getResponse()->getValue('headers'); + $headers = $this->getContext()->getResponse()->getValue('headers') ?? []; $normalized = array_change_key_case($headers, CASE_LOWER); return $normalized['twn-request-id'][0] ?? ''; diff --git a/src/Model/Catalog/Layer/Url.php b/src/Model/Catalog/Layer/Url.php index 2c4925f6..c1a7ff50 100644 --- a/src/Model/Catalog/Layer/Url.php +++ b/src/Model/Catalog/Layer/Url.php @@ -10,18 +10,22 @@ namespace Tweakwise\Magento2Tweakwise\Model\Catalog\Layer; use Tweakwise\Magento2Tweakwise\Model\Catalog\Layer\Filter\Item; +use Tweakwise\Magento2Tweakwise\Model\Catalog\Layer\NavigationContext\CurrentContext; use Tweakwise\Magento2Tweakwise\Model\Catalog\Layer\Url\CategoryUrlInterface; use Tweakwise\Magento2Tweakwise\Model\Catalog\Layer\Url\FilterApplierInterface; use Tweakwise\Magento2Tweakwise\Model\Catalog\Layer\Url\Strategy\UrlStrategyFactory; use Tweakwise\Magento2Tweakwise\Model\Catalog\Layer\Url\UrlInterface; use Tweakwise\Magento2Tweakwise\Model\Catalog\Layer\Url\UrlModel; use Tweakwise\Magento2Tweakwise\Model\Client\Request\ProductNavigationRequest; +use Tweakwise\Magento2Tweakwise\Model\Client\Request\ProductSearchRequest; use Tweakwise\Magento2Tweakwise\Model\Client\Type\FacetType\SettingsType; +use Tweakwise\Magento2Tweakwise\Model\Config; use Tweakwise\Magento2TweakwiseExport\Model\Helper as ExportHelper; use Magento\Catalog\Api\CategoryRepositoryInterface; use Magento\Catalog\Api\Data\CategoryInterface; use Magento\Framework\App\Request\Http as MagentoHttpRequest; use Magento\Framework\Exception\NoSuchEntityException; +use Exception; /** * Class Url will later implement logic to use implementation selected in configuration. @@ -72,13 +76,18 @@ class Url * @param MagentoHttpRequest $request * @param CategoryRepositoryInterface $categoryRepository * @param ExportHelper $exportHelper + * @param UrlModel $magentoUrl + * @param Config $config + * @param CurrentContext $currentContext */ public function __construct( UrlStrategyFactory $urlStrategyFactory, MagentoHttpRequest $request, CategoryRepositoryInterface $categoryRepository, ExportHelper $exportHelper, - UrlModel $magentoUrl + UrlModel $magentoUrl, + protected readonly Config $config, + protected readonly CurrentContext $currentContext ) { $this->urlStrategyFactory = $urlStrategyFactory; $this->categoryRepository = $categoryRepository; @@ -143,6 +152,10 @@ public function getSelectFilter(Item $item): string ->getFacetSettings(); if ($settings->getSource() === SettingsType::SOURCE_CATEGORY) { + if ($this->shouldUseTweakwiseCategoryUrl($item)) { + return $item->getAttribute()->getLink(); + } + return $this->getCategoryUrlStrategy() ->getCategoryFilterSelectUrl($this->request, $item); } @@ -150,6 +163,36 @@ public function getSelectFilter(Item $item): string return $this->addBaseUrl($this->getUrlStrategy()->getAttributeSelectUrl($this->request, $item)); } + /** + * Determine whether the Tweakwise-provided category URL should be used for a filter item. + * Returns true when: + * - the "Use category URL from Tweakwise" setting is enabled + * - the item carries a non-empty link from Tweakwise + * - the current request is not a search request + * + * @param Item $item + * @return bool + */ + protected function shouldUseTweakwiseCategoryUrl(Item $item): bool + { + if (!$this->config->isCategoryUrlFromTweakwiseEnabled()) { + return false; + } + + $tweakwiseUrl = $item->getAttribute()->getLink(); + if (empty($tweakwiseUrl)) { + return false; + } + + try { + $navigationRequest = $this->currentContext->getRequest(); + } catch (Exception $e) { + return false; + } + + return !($navigationRequest instanceof ProductSearchRequest); + } + /** * {@inheritdoc} */ diff --git a/src/Model/Client/Type/AttributeType.php b/src/Model/Client/Type/AttributeType.php index 5e577b04..6ef87339 100644 --- a/src/Model/Client/Type/AttributeType.php +++ b/src/Model/Client/Type/AttributeType.php @@ -33,11 +33,11 @@ public function setChildren(array $children) } /** - * @return string + * @return AttributeType[] */ - public function getChildren() + public function getChildren(): array { - return $this->getDataValue('children'); + return $this->getDataValue('children') ?? []; } /** @@ -82,6 +82,14 @@ public function getUrl() return (string) $this->getDataValue('url'); } + /** + * @return string + */ + public function getLink(): string + { + return (string) $this->getDataValue('link'); + } + /** * @return int|null */ diff --git a/src/Model/Config.php b/src/Model/Config.php index b7cfa4a6..033523ee 100644 --- a/src/Model/Config.php +++ b/src/Model/Config.php @@ -663,6 +663,15 @@ public function isCategoryViewDefault(?Store $store = null) return $this->getStoreConfig('tweakwise/layered/default_category_view', $store); } + /** + * @param Store|null $store + * @return bool + */ + public function isCategoryUrlFromTweakwiseEnabled(?Store $store = null): bool + { + return (bool) $this->getStoreConfig('tweakwise/layered/use_category_url_from_tweakwise', $store); + } + /** * @return int */ diff --git a/src/etc/adminhtml/system.xml b/src/etc/adminhtml/system.xml index 3c1c19ff..78d81aeb 100644 --- a/src/etc/adminhtml/system.xml +++ b/src/etc/adminhtml/system.xml @@ -116,6 +116,14 @@ 0 + + + When enabled, category facet links use the URL provided by Tweakwise instead of Magento-generated URLs. Only applies on category pages, not on search results. + Magento\Config\Model\Config\Source\Yesno + + 1 + + diff --git a/tests/Functional/CategoryLinkTest.php b/tests/Functional/CategoryLinkTest.php new file mode 100644 index 00000000..af6e2f9d --- /dev/null +++ b/tests/Functional/CategoryLinkTest.php @@ -0,0 +1,199 @@ +category = new CategoryFixture('Women Tops'); + $this->tester->createFixture($this->category); + + $this->tester->mockConfig('tweakwise/general/enabled', '1'); + $this->tester->mockConfig('tweakwise/layered/enabled', '1'); + } + + /** + * When the setting is disabled, category facet links must resolve to the store domain, + * not the raw Tweakwise link field. + * + * @return void + */ + public function testCategoryFacetLinksUseMagentoUrlWhenSettingDisabled(): void + { + $this->tester->mockConfig('tweakwise/layered/use_category_url_from_tweakwise', '0'); + $this->mockClientWithCategoryFacet(self::TWEAKWISE_LINK); + + $this->tester->amOnPage('/' . $this->category->getUrlKey() . '.html'); + + // Magento store domain must appear in the page (navigation rendered). + $this->tester->seeInSource('tweakwise.test'); + // A double-domain href indicates the raw Tweakwise URL leaked into a Magento-built URL. + $this->tester->dontSeeInSource('tweakwise.test/default/http'); + $this->tester->dontSeeInSource('tweakwise.test/default/https'); + } + + /** + * When the setting is enabled, category facet links must use the Tweakwise-provided link field. + * + * @return void + */ + public function testCategoryFacetLinksUseTweakwiseUrlWhenSettingEnabled(): void + { + $this->tester->mockConfig('tweakwise/layered/use_category_url_from_tweakwise', '1'); + $this->mockClientWithCategoryFacet(self::TWEAKWISE_LINK); + + $this->tester->amOnPage('/' . $this->category->getUrlKey() . '.html'); + + $this->tester->seeInSource('tweakwise.test'); + } + + /** + * No duplicated domain must appear regardless of the setting value. + * + * @return void + */ + public function testCategoryFacetLinksContainNoDuplicatedDomain(): void + { + $this->tester->mockConfig('tweakwise/layered/use_category_url_from_tweakwise', '1'); + $this->mockClientWithCategoryFacet(self::TWEAKWISE_LINK); + + $this->tester->amOnPage('/' . $this->category->getUrlKey() . '.html'); + + $this->tester->dontSeeInSource('tweakwise.testtweakwise.test'); + $this->tester->dontSeeInSource('tweakwise.test//'); + } + + /** + * On the search results page the raw Tweakwise link field must never be used, + * even when the setting is enabled. + * + * @return void + */ + public function testCategoryFacetLinksUseMagentoUrlOnSearchResultsPageEvenWhenEnabled(): void + { + $this->tester->mockConfig('tweakwise/layered/use_category_url_from_tweakwise', '1'); + $this->mockClientWithCategoryFacet(self::TWEAKWISE_LINK); + + $this->tester->amOnPage('/catalogsearch/result/?q=tops'); + + // Search page must not produce a raw category link href. + $this->tester->seeInSource('catalogsearch'); + $this->tester->dontSeeInSource(self::TWEAKWISE_LINK); + } + + /** + * When Tweakwise returns an empty link field the Magento URL must be used as fallback. + * + * @return void + */ + public function testCategoryFacetLinkFallsBackToMagentoUrlWhenTweakwiseLinkIsEmpty(): void + { + $this->tester->mockConfig('tweakwise/layered/use_category_url_from_tweakwise', '1'); + $this->mockClientWithCategoryFacet(''); + + $this->tester->amOnPage('/' . $this->category->getUrlKey() . '.html'); + + // With an empty link the rendered href must not contain a bare slash-slash or be empty. + $this->tester->dontSeeInSource('href=""'); + $this->tester->dontSeeInSource('tweakwise.test//'); + } + + /** + * Build a mocked Client that returns a ProductNavigationResponse containing a single + * category facet with one attribute item whose link field equals $categoryLink. + * + * @param string $categoryLink Value for the `link` field on the attribute item. + * @return void + */ + private function mockClientWithCategoryFacet(string $categoryLink): void + { + $response = $this->buildNavigationResponse($categoryLink); + + $client = Mockery::mock(Client::class); + $client->shouldReceive('request')->andReturn($response); + + $this->tester->mockService(Client::class, $client); + } + + /** + * Build a minimal ProductNavigationResponse with one category facet containing one item. + * + * @param string $categoryLink + * @return ProductNavigationResponse + */ + private function buildNavigationResponse(string $categoryLink): ProductNavigationResponse + { + $facetData = [ + 'facetsettings' => [ + 'source' => SettingsType::SOURCE_CATEGORY, + 'title' => 'Category', + 'urlkey' => 'cat', + 'selectiontype' => SettingsType::SELECTION_TYPE_LINK, + 'isnrofresultsvisible' => 'false', + 'ismultiselect' => 'false', + 'iscollapsible' => 'false', + 'iscollapsed' => 'false', + 'isinfivisible' => 'false', + 'isvisible' => 'true', + 'nrofshownattributes' => 10, + ], + 'attributes' => [ + [ + 'title' => 'Tops', + 'url' => 'https://tweakwise.test/default/women/tops-women2/', + 'link' => $categoryLink, + 'nrofresults' => 12, + 'isselected' => 'false', + 'attributeid' => '100042', + ], + ], + ]; + + $facet = new FacetType($facetData); + + /** @var ProductNavigationResponse $response */ + $response = $this->tester->getObjectManager()->create( + ProductNavigationResponse::class, + [ + 'data' => [ + 'facets' => [$facet], + 'items' => [], + 'properties' => [ + 'nrofitems' => 0, + 'nrofpages' => 0, + 'currentpage' => 1, + 'nrofitemsperpage' => 16, + 'selectedcategoryid' => null, + ], + ], + ] + ); + + return $response; + } +} diff --git a/tests/Functional/InitialTest.php b/tests/Functional/InitialTest.php deleted file mode 100644 index f71fd2fd..00000000 --- a/tests/Functional/InitialTest.php +++ /dev/null @@ -1,24 +0,0 @@ -tester->assertTrue(true); - } -} diff --git a/tests/Unit.suite.yml b/tests/Unit.suite.yml index 76c1b979..2ed0be8b 100644 --- a/tests/Unit.suite.yml +++ b/tests/Unit.suite.yml @@ -1,5 +1,48 @@ actor: UnitTester suite_namespace: Tweakwise\Test\Unit modules: - # enable helpers as array - enabled: [] + enabled: + - Asserts + - \Emico\CodeCept\Module\Magento2 + - \Emico\CodeCept\Module\Mocking + - \Emico\CodeCept\Module\Fixtures + - \Emico\CodeCept\Module\Config + - \Emico\CodeCept\Module\Queue + config: + \Emico\CodeCept\Module\Magento2: + url: 'https://tweakwise.test' + store_code: 'default' + configuration_bootstrap: + queue: + amqp: + host: 127.0.0.1 + port: 5672 + user: tweakwise + password: tweakwise + virtualhost: tweakwise + configuration: + default: + web: + unsecure: + base_url: 'https://tweakwise.test/' + secure: + base_url: 'https://tweakwise.test/' + websites: + magento: + web: + unsecure: + base_url: 'https://tweakwise.test/' + secure: + base_url: 'https://tweakwise.test/' + \Emico\CodeCept\Module\Config: + depends: + - \Emico\CodeCept\Module\Magento2 + \Emico\CodeCept\Module\Mocking: + depends: + - \Emico\CodeCept\Module\Magento2 + \Emico\CodeCept\Module\Fixtures: + depends: + - \Emico\CodeCept\Module\Magento2 + \Emico\CodeCept\Module\Queue: + depends: + - \Emico\CodeCept\Module\Magento2 diff --git a/tests/Unit/Block/LayeredNavigation/RenderLayered/DefaultRendererTest.php b/tests/Unit/Block/LayeredNavigation/RenderLayered/DefaultRendererTest.php new file mode 100644 index 00000000..0c6cfe81 --- /dev/null +++ b/tests/Unit/Block/LayeredNavigation/RenderLayered/DefaultRendererTest.php @@ -0,0 +1,81 @@ +mockRenderer(); + } + + /** + * @return void + */ + public function testGetCategoryUrlDoesNotPrependDomainWhenOnlySchemeDiffers(): void + { + $url = 'http://magento2.test/default/women/tops-women2/'; + $item = $this->mockItem($url); + + $this->assertEquals( + $url, + $this->renderer->getCategoryUrl($item) + ); + } + + /** + * @return void + */ + public function testGetCategoryUrlPrependsBaseUrlForRelativeFacetLink(): void + { + $item = $this->mockItem('/default/women/tops-women2/'); + + $this->assertEquals( + 'https://magento2.test/default/women/tops-women2/', + $this->renderer->getCategoryUrl($item) + ); + } + + /** + * @return void + */ + private function mockRenderer(): void + { + $renderer = Mockery::mock(DefaultRenderer::class)->makePartial(); + $renderer->shouldReceive('getBaseUrl')->andReturn('https://magento2.test/'); + + $this->renderer = $renderer; + } + + /** + * @param string $url + * @return Item|MockInterface + */ + private function mockItem(string $url): Item|MockInterface + { + $item = Mockery::mock(Item::class); + $item->shouldReceive('getUrl')->andReturn($url); + + return $item; + } +} diff --git a/tests/Unit/Model/Catalog/Layer/UrlTest.php b/tests/Unit/Model/Catalog/Layer/UrlTest.php new file mode 100644 index 00000000..7a37ba52 --- /dev/null +++ b/tests/Unit/Model/Catalog/Layer/UrlTest.php @@ -0,0 +1,220 @@ +urlStrategyFactory = Mockery::mock(UrlStrategyFactory::class); + $this->request = Mockery::mock(MagentoHttpRequest::class); + $this->config = Mockery::mock(Config::class); + $this->currentContext = Mockery::mock(CurrentContext::class); + $this->categoryUrlStrategy = Mockery::mock(CategoryUrlInterface::class); + + $this->subject = new Url( + $this->urlStrategyFactory, + $this->request, + Mockery::mock(CategoryRepositoryInterface::class), + Mockery::mock(ExportHelper::class), + Mockery::mock(UrlModel::class), + $this->config, + $this->currentContext, + ); + } + + /** + * @return void + */ + public function testGetSelectFilterReturnsTweakwiseCategoryLinkOnCategoryPageWhenEnabled(): void + { + $item = $this->createCategoryItem('https://magento2.test/default/women/tops-women2/'); + $this->config->shouldReceive('isCategoryUrlFromTweakwiseEnabled')->andReturn(true); + $this->currentContext->shouldReceive('getRequest')->andReturn(Mockery::mock(ProductNavigationRequest::class)); + $this->urlStrategyFactory->shouldNotReceive('create'); + + $result = $this->subject->getSelectFilter($item); + + $this->assertEquals('https://magento2.test/default/women/tops-women2/', $result); + } + + /** + * @return void + */ + public function testGetSelectFilterFallsBackToMagentoCategoryUrlWhenSettingIsDisabled(): void + { + $item = $this->createCategoryItem('https://magento2.test/default/women/tops-women2/'); + $this->config->shouldReceive('isCategoryUrlFromTweakwiseEnabled')->andReturn(false); + $this->currentContext->shouldNotReceive('getRequest'); + $this->urlStrategyFactory + ->shouldReceive('create') + ->with(CategoryUrlInterface::class) + ->once() + ->andReturn($this->categoryUrlStrategy); + $this->categoryUrlStrategy + ->shouldReceive('getCategoryFilterSelectUrl') + ->with($this->request, $item) + ->once() + ->andReturn('/default/women/tops-women2/facet/'); + + $result = $this->subject->getSelectFilter($item); + + $this->assertEquals('/default/women/tops-women2/facet/', $result); + } + + /** + * @return void + */ + public function testGetSelectFilterFallsBackToMagentoCategoryUrlWhenLinkIsEmpty(): void + { + $item = $this->createCategoryItem(''); + $this->config->shouldReceive('isCategoryUrlFromTweakwiseEnabled')->andReturn(true); + $this->currentContext->shouldNotReceive('getRequest'); + $this->urlStrategyFactory + ->shouldReceive('create') + ->with(CategoryUrlInterface::class) + ->once() + ->andReturn($this->categoryUrlStrategy); + $this->categoryUrlStrategy + ->shouldReceive('getCategoryFilterSelectUrl') + ->with($this->request, $item) + ->once() + ->andReturn('/default/women/tops-women2/facet/'); + + $result = $this->subject->getSelectFilter($item); + + $this->assertEquals('/default/women/tops-women2/facet/', $result); + } + + /** + * @return void + */ + public function testGetSelectFilterFallsBackToMagentoCategoryUrlOnSearchRequest(): void + { + $item = $this->createCategoryItem('https://magento2.test/default/women/tops-women2/'); + $this->config->shouldReceive('isCategoryUrlFromTweakwiseEnabled')->andReturn(true); + $this->currentContext->shouldReceive('getRequest')->andReturn(Mockery::mock(ProductSearchRequest::class)); + $this->urlStrategyFactory + ->shouldReceive('create') + ->with(CategoryUrlInterface::class) + ->once() + ->andReturn($this->categoryUrlStrategy); + $this->categoryUrlStrategy + ->shouldReceive('getCategoryFilterSelectUrl') + ->with($this->request, $item) + ->once() + ->andReturn('/catalogsearch/result/?cat=tops-women2'); + + $result = $this->subject->getSelectFilter($item); + + $this->assertEquals('/catalogsearch/result/?cat=tops-women2', $result); + } + + /** + * @return void + */ + public function testGetSelectFilterFallsBackToMagentoCategoryUrlWhenContextIsUnavailable(): void + { + $item = $this->createCategoryItem('https://magento2.test/default/women/tops-women2/'); + $this->config->shouldReceive('isCategoryUrlFromTweakwiseEnabled')->andReturn(true); + $this->currentContext->shouldReceive('getRequest')->andThrow(new Exception('No active context')); + $this->urlStrategyFactory + ->shouldReceive('create') + ->with(CategoryUrlInterface::class) + ->once() + ->andReturn($this->categoryUrlStrategy); + $this->categoryUrlStrategy + ->shouldReceive('getCategoryFilterSelectUrl') + ->with($this->request, $item) + ->once() + ->andReturn('/default/women/tops-women2/facet/'); + + $result = $this->subject->getSelectFilter($item); + + $this->assertEquals('/default/women/tops-women2/facet/', $result); + } + + /** + * @param string $link + * @return Item|MockInterface + */ + private function createCategoryItem(string $link): Item|MockInterface + { + $settings = new SettingsType(['source' => SettingsType::SOURCE_CATEGORY]); + + $facetType = new FacetType(); + $facetType->setFacetSettings($settings); + + $filter = Mockery::mock(Filter::class); + $filter->shouldReceive('getFacet')->andReturn($facetType); + + $attribute = Mockery::mock(AttributeType::class); + $attribute->shouldReceive('getLink')->andReturn($link); + + $item = Mockery::mock(Item::class); + $item->shouldReceive('getFilter')->andReturn($filter); + $item->shouldReceive('getAttribute')->andReturn($attribute); + + return $item; + } +} diff --git a/tests/Unit/Model/Client/Type/AttributeTypeTest.php b/tests/Unit/Model/Client/Type/AttributeTypeTest.php new file mode 100644 index 00000000..c7992bb3 --- /dev/null +++ b/tests/Unit/Model/Client/Type/AttributeTypeTest.php @@ -0,0 +1,31 @@ + 'https://magento2.test/default/women/tops-women2/']); + + $this->assertSame('https://magento2.test/default/women/tops-women2/', $attribute->getLink()); + } + + /** + * @return void + */ + public function testGetLinkReturnsEmptyStringWhenLinkIsMissing(): void + { + $attribute = new AttributeType(); + + $this->assertSame('', $attribute->getLink()); + } +}