Skip to content

Commit 3be8aa0

Browse files
authored
LYNX-603: Product attribute > trademark short form ™ is returned as ™[Fix]
1 parent 8f401aa commit 3be8aa0

File tree

3 files changed

+191
-3
lines changed

3 files changed

+191
-3
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
/**
3+
* Copyright 2024 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\CatalogGraphQl\Model\Resolver\Product;
9+
10+
use Magento\Framework\Escaper;
11+
use Magento\Framework\Exception\LocalizedException;
12+
use Magento\Framework\GraphQl\Config\Element\Field;
13+
use Magento\Framework\GraphQl\Query\ResolverInterface;
14+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
15+
16+
class ProductName implements ResolverInterface
17+
{
18+
/**
19+
* ProductName constructor
20+
*
21+
* @param Escaper $escaper
22+
*/
23+
public function __construct(
24+
private readonly Escaper $escaper
25+
) {
26+
}
27+
28+
/**
29+
* @inheritdoc
30+
*
31+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
32+
*/
33+
public function resolve(
34+
Field $field,
35+
$context,
36+
ResolveInfo $info,
37+
array $value = null,
38+
array $args = null
39+
): string {
40+
if (!isset($value['model'])) {
41+
throw new LocalizedException(__('"model" value should be specified'));
42+
}
43+
44+
// Product name allowed with special characters
45+
return str_replace(
46+
"&apos;",
47+
"'",
48+
str_replace("&amp;", "&", $this->escaper->escapeUrl($value['model']->getName()))
49+
);
50+
}
51+
}

app/code/Magento/CatalogGraphQl/etc/schema.graphqls

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
# Copyright © Magento, Inc. All rights reserved.
2-
# See COPYING.txt for license details.
1+
# Copyright 2024 Adobe
2+
# All Rights Reserved.
33

44
type Query {
55
products (
@@ -93,7 +93,7 @@ interface ProductLinksInterface @typeResolver(class: "Magento\\CatalogGraphQl\\M
9393
interface ProductInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\\ProductInterfaceTypeResolverComposite") @doc(description: "Contains fields that are common to all types of products.") {
9494
id: Int @deprecated(reason: "Use the `uid` field instead.") @doc(description: "The ID number assigned to the product.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\EntityIdToId")
9595
uid: ID! @doc(description: "The unique ID for a `ProductInterface` object.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\EntityIdToUid")
96-
name: String @doc(description: "The product name. Customers use this name to identify the product.")
96+
name: String @doc(description: "The product name. Customers use this name to identify the product.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\ProductName")
9797
sku: String @doc(description: "A number or code assigned to a product to identify the product, options, price, and manufacturer.")
9898
description: ComplexTextValue @doc(description: "Detailed information about the product. The value can include simple HTML tags.") @resolver(class: "\\Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\ProductComplexTextAttribute")
9999
short_description: ComplexTextValue @doc(description: "A short description of the product. Its use depends on the theme.") @resolver(class: "\\Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\ProductComplexTextAttribute")
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
<?php
2+
/**
3+
* Copyright 2024 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\GraphQl\Catalog;
9+
10+
use Magento\Catalog\Test\Fixture\Product as ProductFixture;
11+
use Magento\Framework\Exception\NoSuchEntityException;
12+
use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface;
13+
use Magento\Quote\Test\Fixture\GuestCart as GuestCartFixture;
14+
use Magento\TestFramework\Fixture\DataFixture;
15+
use Magento\TestFramework\Fixture\DataFixtureStorage;
16+
use Magento\TestFramework\Fixture\DataFixtureStorageManager;
17+
use Magento\TestFramework\Helper\Bootstrap;
18+
use Magento\TestFramework\TestCase\GraphQlAbstract;
19+
20+
class ProductNameWithSpecialCharactersTest extends GraphQlAbstract
21+
{
22+
/**
23+
* @var QuoteIdToMaskedQuoteIdInterface
24+
*/
25+
private $quoteIdToMaskedQuoteId;
26+
27+
/**
28+
* @var DataFixtureStorage
29+
*/
30+
private $fixtures;
31+
32+
/**
33+
* @inheritdoc
34+
*/
35+
protected function setUp(): void
36+
{
37+
$this->quoteIdToMaskedQuoteId = Bootstrap::getObjectManager()->get(QuoteIdToMaskedQuoteIdInterface::class);
38+
$this->fixtures = Bootstrap::getObjectManager()->get(DataFixtureStorageManager::class)->getStorage();
39+
}
40+
41+
/**
42+
* Test product name with special characters
43+
*
44+
* @param string $sku
45+
* @param string $expectedName
46+
* @throws NoSuchEntityException
47+
* @dataProvider productNameProvider
48+
*/
49+
#[
50+
DataFixture(ProductFixture::class, [
51+
'sku' => 'test-product-1',
52+
'name' => 'Test Product© 1'
53+
]),
54+
DataFixture(ProductFixture::class, [
55+
'sku' => 'test-product-2',
56+
'name' => 'Test Product™ 2'
57+
]),
58+
DataFixture(ProductFixture::class, [
59+
'sku' => 'test-product-3',
60+
'name' => 'Sample Product&copy; 3'
61+
]),
62+
DataFixture(ProductFixture::class, [
63+
'sku' => 'test-product-4',
64+
'name' => 'Sample Product&trade; 4'
65+
]),
66+
DataFixture(ProductFixture::class, [
67+
'sku' => 'test-product-5',
68+
'name' => 'Test Product 5'
69+
]),
70+
DataFixture(GuestCartFixture::class, as: 'cart')
71+
]
72+
public function testProductName(string $sku, string $expectedName): void
73+
{
74+
$cart = $this->fixtures->get('cart');
75+
$maskedQuoteId = $this->quoteIdToMaskedQuoteId->execute((int) $cart->getId());
76+
77+
$query = $this->getAddToCartMutation($maskedQuoteId, $sku);
78+
$response = $this->graphQlMutation($query);
79+
$result = $response['addProductsToCart'];
80+
81+
self::assertCount(1, $result['cart']['items']);
82+
self::assertEquals(1, $result['cart']['items'][0]['quantity']);
83+
self::assertEquals($expectedName, $result['cart']['items'][0]['product']['name']);
84+
}
85+
86+
/**
87+
* Data provider for product name test cases
88+
*
89+
* @return array[]
90+
*/
91+
public static function productNameProvider(): array
92+
{
93+
return [
94+
['test-product-1', 'Test Product© 1'],
95+
['test-product-2', 'Test Product™ 2'],
96+
['test-product-3', 'Sample Product© 3'],
97+
['test-product-4', 'Sample Product™ 4'],
98+
['test-product-5', 'Test Product 5']
99+
];
100+
}
101+
102+
/**
103+
* Returns GraphQl mutation
104+
*
105+
* @param string $maskedQuoteId
106+
* @param string $sku
107+
* @return string
108+
*/
109+
private function getAddToCartMutation(string $maskedQuoteId, string $sku): string
110+
{
111+
return <<<MUTATION
112+
mutation {
113+
addProductsToCart(
114+
cartId: "{$maskedQuoteId}",
115+
cartItems: [
116+
{
117+
sku: "{$sku}"
118+
quantity: 1
119+
}
120+
]
121+
) {
122+
cart {
123+
id
124+
items {
125+
uid
126+
quantity
127+
product {
128+
sku
129+
name
130+
}
131+
}
132+
}
133+
}
134+
}
135+
MUTATION;
136+
}
137+
}

0 commit comments

Comments
 (0)