Skip to content

Commit ab89130

Browse files
Merge pull request #10145 from adobe-commerce-tier-4/PR_2025_10_16_muntianu
[Support Tier-4 muntianu] 10-16-2025 Regular delivery of bugfixes and improvements
2 parents 44b494e + 2f93e3b commit ab89130

File tree

16 files changed

+571
-276
lines changed

16 files changed

+571
-276
lines changed

app/code/Magento/Bundle/Model/LinkManagement.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,9 @@ private function processLinkedProduct(
252252
throw new CouldNotSaveException(__('Could not save child: "%1"', $e->getMessage()), $e);
253253
}
254254

255+
$linkedProduct->setId($selectionModel->getId());
256+
$linkedProduct->setSelectionId($selectionModel->getId());
257+
$linkedProduct->setOptionId($optionId);
255258
return (int)$selectionModel->getId();
256259
}
257260

@@ -450,14 +453,16 @@ public function removeChild($sku, $optionId, $childSku)
450453
$excludeSelectionIds = [];
451454
$usedProductIds = [];
452455
$removeSelectionIds = [];
456+
$removeProductIds = [];
453457
foreach ($this->getOptions($product) as $option) {
454458
/** @var Selection $selection */
455459
foreach ($option->getSelections() as $selection) {
456460
if ((strcasecmp($selection->getSku(), $childSku) == 0) && ($selection->getOptionId() == $optionId)) {
457461
$removeSelectionIds[] = $selection->getSelectionId();
458-
$usedProductIds[] = $selection->getProductId();
462+
$removeProductIds[] = $selection->getProductId();
459463
continue;
460464
}
465+
$usedProductIds[] = $selection->getProductId();
461466
$excludeSelectionIds[] = $selection->getSelectionId();
462467
}
463468
}
@@ -470,7 +475,10 @@ public function removeChild($sku, $optionId, $childSku)
470475
/* @var $resource Bundle */
471476
$resource = $this->bundleFactory->create();
472477
$resource->dropAllUnneededSelections($product->getData($linkField), $excludeSelectionIds);
473-
$resource->removeProductRelations($product->getData($linkField), array_unique($usedProductIds));
478+
$productRelationsToRemove = array_diff($removeProductIds, $usedProductIds);
479+
if ($productRelationsToRemove) {
480+
$resource->removeProductRelations($product->getData($linkField), array_unique($productRelationsToRemove));
481+
}
474482

475483
return true;
476484
}

app/code/Magento/Bundle/Model/Product/CopyConstructor/Bundle.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,15 @@ public function build(Product $product, Product $duplicate)
3535
* Set option and selection ids to 'null' in order to create new option(selection) for duplicated product,
3636
* but not modifying existing one, which led to lost of option(selection) in original product.
3737
*/
38-
$productLinks = $duplicatedBundleOption->getProductLinks() ?: [];
39-
foreach ($productLinks as $productLink) {
40-
$productLink->setSelectionId(null);
38+
$productLinks = [];
39+
foreach ($duplicatedBundleOption->getProductLinks() ?: [] as $productLink) {
40+
$productLinkDuplicate = clone $productLink;
41+
$productLinkDuplicate->setId(null);
42+
$productLinkDuplicate->setSelectionId(null);
43+
$productLinkDuplicate->setOptionId(null);
44+
$productLinks[] = $productLinkDuplicate;
4145
}
46+
$duplicatedBundleOption->setProductLinks($productLinks);
4247
$duplicatedBundleOption->setOptionId(null);
4348
$duplicatedBundleOptions[$key] = $duplicatedBundleOption;
4449
}

app/code/Magento/Bundle/Model/Product/SaveHandler.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,7 @@ protected function removeOptionLinks($entitySku, $option)
141141
$links = $option->getProductLinks();
142142
if (!empty($links)) {
143143
foreach ($links as $link) {
144-
$linkCanBeDeleted = $this->checkOptionLinkIfExist->execute($entitySku, $option, $link);
145-
if ($linkCanBeDeleted) {
146-
$this->productLinkManagement->removeChild($entitySku, $option->getId(), $link->getSku());
147-
}
144+
$this->productLinkManagement->removeChild($entitySku, $option->getId(), $link->getSku());
148145
}
149146
}
150147
}

app/code/Magento/Bundle/Test/Unit/Model/LinkManagementTest.php

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -639,14 +639,11 @@ static function () {
639639
*/
640640
public function testAddChild(): void
641641
{
642-
$productLink = $this->getMockBuilder(LinkInterface::class)
643-
->onlyMethods(['getSku', 'getOptionId'])
644-
->addMethods(['getSelectionId'])
645-
->disableOriginalConstructor()
646-
->getMockForAbstractClass();
647-
$productLink->method('getSku')->willReturn('linked_product_sku');
648-
$productLink->method('getOptionId')->willReturn(1);
649-
$productLink->method('getSelectionId')->willReturn(1);
642+
$selectionId = 42;
643+
$optionId = 1;
644+
$sku = 'linked_product_sku';
645+
$productLink = $this->createPartialMock(\Magento\Bundle\Model\Link::class, []);
646+
$productLink->setSku($sku);
650647

651648
$this->metadataMock->expects($this->exactly(2))->method('getLinkField')->willReturn($this->linkField);
652649
$productMock = $this->createMock(Product::class);
@@ -662,7 +659,7 @@ public function testAddChild(): void
662659
$this->productRepository
663660
->expects($this->once())
664661
->method('get')
665-
->with('linked_product_sku')
662+
->with($sku)
666663
->willReturn($linkedProductMock);
667664

668665
$store = $this->createMock(Store::class);
@@ -698,10 +695,14 @@ public function testAddChild(): void
698695

699696
$selection = $this->createPartialMock(Selection::class, ['save', 'getId', 'load']);
700697
$selection->expects($this->once())->method('save');
701-
$selection->expects($this->once())->method('getId')->willReturn(42);
698+
$selection->expects($this->any())->method('getId')->willReturn($selectionId);
702699
$this->bundleSelectionMock->expects($this->once())->method('create')->willReturn($selection);
703-
$result = $this->model->addChild($productMock, 1, $productLink);
704-
$this->assertEquals(42, $result);
700+
701+
$result = $this->model->addChild($productMock, $optionId, $productLink);
702+
$this->assertEquals($selectionId, $result);
703+
$this->assertEquals($selectionId, $productLink->getId());
704+
$this->assertEquals($selectionId, $productLink->getSelectionId());
705+
$this->assertEquals($optionId, $productLink->getOptionId());
705706
}
706707

707708
/**

app/code/Magento/Bundle/Test/Unit/Model/Product/CopyConstructor/BundleTest.php

Lines changed: 82 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77

88
namespace Magento\Bundle\Test\Unit\Model\Product\CopyConstructor;
99

10-
use Magento\Bundle\Api\Data\BundleOptionInterface;
1110
use Magento\Bundle\Model\Link;
11+
use Magento\Bundle\Model\Option;
1212
use Magento\Bundle\Model\Product\CopyConstructor\Bundle;
1313
use Magento\Catalog\Api\Data\ProductExtensionInterface;
1414
use Magento\Catalog\Model\Product;
@@ -65,38 +65,39 @@ public function testBuildPositive()
6565
$product->expects($this->once())
6666
->method('getExtensionAttributes')
6767
->willReturn($extensionAttributesProduct);
68-
69-
$productLink = $this->getMockBuilder(Link::class)
70-
->addMethods(['setSelectionId'])
71-
->disableOriginalConstructor()
72-
->getMock();
73-
$productLink->expects($this->exactly(2))
74-
->method('setSelectionId')
75-
->with($this->identicalTo(null));
76-
$firstOption = $this->getMockBuilder(BundleOptionInterface::class)
77-
->addMethods(['getProductLinks'])
78-
->disableOriginalConstructor()
79-
->getMockForAbstractClass();
80-
$firstOption->expects($this->once())
81-
->method('getProductLinks')
82-
->willReturn([$productLink]);
83-
$firstOption->expects($this->once())
84-
->method('setOptionId')
85-
->with($this->identicalTo(null));
86-
$secondOption = $this->getMockBuilder(BundleOptionInterface::class)
87-
->addMethods(['getProductLinks'])
88-
->disableOriginalConstructor()
89-
->getMockForAbstractClass();
90-
$secondOption->expects($this->once())
91-
->method('getProductLinks')
92-
->willReturn([$productLink]);
93-
$secondOption->expects($this->once())
94-
->method('setOptionId')
95-
->with($this->identicalTo(null));
96-
$bundleOptions = [
97-
$firstOption,
98-
$secondOption
68+
69+
$bundleOptionsData = [
70+
[
71+
'option_id' => 1,
72+
'title' => 'Option 1',
73+
'product_links' => [
74+
[
75+
'option_id' => 1,
76+
'id' => 1,
77+
'selection_id' => 1,
78+
'sku' => 'sku-1'
79+
],
80+
]
81+
],
82+
[
83+
'option_id' => 2,
84+
'title' => 'Option 2',
85+
'product_links' => [
86+
[
87+
'option_id' => 2,
88+
'id' => 2,
89+
'selection_id' => 2,
90+
'sku' => 'sku-2'
91+
]
92+
]
93+
]
9994
];
95+
$bundleOptions = array_map(
96+
fn ($optionData) => $this->createOptionMock(
97+
[...$optionData, 'product_links' => array_map($this->createLinkMock(...), $optionData['product_links'])]
98+
),
99+
$bundleOptionsData
100+
);
100101
$extensionAttributesProduct->expects($this->once())
101102
->method('getBundleProductOptions')
102103
->willReturn($bundleOptions);
@@ -115,13 +116,57 @@ public function testBuildPositive()
115116
->willReturn($extensionAttributesDuplicate);
116117
$extensionAttributesDuplicate->expects($this->once())
117118
->method('setBundleProductOptions')
118-
->willReturnCallback(function ($bundleOptions) {
119-
if ($bundleOptions) {
120-
return null;
121-
}
122-
});
119+
->with(
120+
$this->callback(function ($options) use (&$bundleOptionsClone) {
121+
$bundleOptionsClone = $options;
122+
return !empty($bundleOptionsClone);
123+
})
124+
);
123125

124126
$this->model->build($product, $duplicate);
127+
foreach ($bundleOptionsData as $key => $optionData) {
128+
$bundleOption = $bundleOptions[$key];
129+
$bundleOptionClone = $bundleOptionsClone[$key];
130+
131+
$this->assertEquals($optionData['option_id'], $bundleOption->getOptionId());
132+
$this->assertEquals($optionData['title'], $bundleOption->getTitle());
133+
134+
$this->assertNotEquals($bundleOption, $bundleOptionClone);
135+
136+
$this->assertNull($bundleOptionClone->getOptionId());
137+
$this->assertEquals($optionData['title'], $bundleOptionClone->getTitle());
138+
139+
foreach ($optionData['product_links'] as $productLinkKey => $productLinkData) {
140+
$productLink = $bundleOption->getProductLinks()[$productLinkKey];
141+
$productLinkClone = $bundleOptionClone->getProductLinks()[$productLinkKey];
142+
143+
$this->assertEquals($productLinkData['option_id'], $productLink->getOptionId());
144+
$this->assertEquals($productLinkData['id'], $productLink->getId());
145+
$this->assertEquals($productLinkData['selection_id'], $productLink->getSelectionId());
146+
$this->assertEquals($productLinkData['sku'], $productLink->getSku());
147+
148+
$this->assertNotEquals($productLink, $productLinkClone);
149+
150+
$this->assertNull($productLinkClone->getId());
151+
$this->assertNull($productLinkClone->getOptionId());
152+
$this->assertNull($productLinkClone->getSelectionId());
153+
$this->assertEquals($productLinkData['sku'], $productLinkClone->getSku());
154+
}
155+
}
156+
}
157+
158+
private function createOptionMock(array $data): Option
159+
{
160+
$option = $this->createPartialMock(Option::class, []);
161+
$option->addData($data);
162+
return $option;
163+
}
164+
165+
private function createLinkMock(array $data): Link
166+
{
167+
$productLink = $this->createPartialMock(Link::class, []);
168+
$productLink->addData($data);
169+
return $productLink;
125170
}
126171

127172
/**

app/code/Magento/Catalog/Test/Unit/Block/Category/ViewTest.php

Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,24 @@
77

88
namespace Magento\Catalog\Test\Unit\Block\Category;
99

10+
use Magento\Catalog\Block\Breadcrumbs;
1011
use Magento\Catalog\Block\Category\View;
12+
use Magento\Catalog\Helper\Category as CategoryHelper;
13+
use Magento\Catalog\Helper\Data as CatalogData;
1114
use Magento\Catalog\Model\Category;
15+
use Magento\Catalog\Model\Layer\Resolver as LayerResolver;
1216
use Magento\Framework\App\Config\ScopeConfigInterface;
13-
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
17+
use Magento\Framework\Registry;
18+
use Magento\Framework\View\Element\Template\Context;
19+
use Magento\Framework\View\LayoutInterface;
20+
use Magento\Framework\View\Page\Config;
21+
use Magento\Framework\View\Page\Title;
1422
use PHPUnit\Framework\MockObject\MockObject;
1523
use PHPUnit\Framework\TestCase;
1624

25+
/**
26+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
27+
*/
1728
class ViewTest extends TestCase
1829
{
1930
/**
@@ -26,10 +37,40 @@ class ViewTest extends TestCase
2637
*/
2738
protected $scopeConfigMock;
2839

40+
/**
41+
* @var Registry|MockObject
42+
*/
43+
private $registry;
44+
45+
/**
46+
* @var Config|MockObject
47+
*/
48+
private $config;
49+
50+
/**
51+
* @var CatalogData|MockObject
52+
*/
53+
private $catalogData;
2954
protected function setUp(): void
3055
{
31-
$objectManager = new ObjectManager($this);
32-
$this->block = $objectManager->getObject(View::class, []);
56+
parent::setUp();
57+
$context = $this->createMock(Context::class);
58+
$this->config = $this->createMock(Config::class);
59+
$context->expects($this->once())->method('getPageConfig')
60+
->willReturn($this->config);
61+
$layerResolver = $this->createMock(LayerResolver::class);
62+
$this->registry = $this->createMock(Registry::class);
63+
$categoryHelper = $this->createMock(CategoryHelper::class);
64+
$this->catalogData = $this->createMock(CatalogData::class);
65+
66+
$this->block = new View(
67+
$context,
68+
$layerResolver,
69+
$this->registry,
70+
$categoryHelper,
71+
[],
72+
$this->catalogData
73+
);
3374
}
3475

3576
protected function tearDown(): void
@@ -45,4 +86,43 @@ public function testGetIdentities()
4586
$this->block->setCurrentCategory($currentCategoryMock);
4687
$this->assertEquals($categoryTag, $this->block->getIdentities());
4788
}
89+
90+
public function testBreadcrumbs()
91+
{
92+
$layoutMock = $this->createMock(LayoutInterface::class);
93+
$beadCrumbs = $this->createMock(Breadcrumbs::class);
94+
$title = $this->createMock(Title::class);
95+
$category = $this->getMockBuilder(Category::class)
96+
->disableOriginalConstructor()
97+
->addMethods(['getMetaTitle'])
98+
->getMock();
99+
100+
$beadCrumbs->expects($this->once())
101+
->method('getTitleSeparator')
102+
->willReturn(' - ');
103+
104+
$layoutMock->expects($this->once())
105+
->method('createBlock')
106+
->willReturn($beadCrumbs);
107+
108+
$category->expects($this->once())
109+
->method('getMetaTitle')
110+
->willReturn(null);
111+
$this->registry->expects($this->once())
112+
->method('registry')
113+
->willReturn($category);
114+
115+
$title->expects($this->once())
116+
->method('set')
117+
->willReturnSelf();
118+
$this->config->expects($this->once())
119+
->method('getTitle')
120+
->willReturn($title);
121+
122+
$this->catalogData->expects($this->once())
123+
->method('getBreadcrumbPath')
124+
->willReturn([['label' => 'label1'], ['label' => 'label2']]);
125+
126+
$this->block->setLayout($layoutMock);
127+
}
48128
}

0 commit comments

Comments
 (0)