diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 0000000000000..10589b97ea9b3 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,68 @@ +# Configuration for probot-stale - https://github.com/probot/stale + +# Number of days of inactivity before an Issue or Pull Request becomes stale + daysUntilStale: 76 + +# Number of days of inactivity before an Issue or Pull Request with the stale label is closed. +# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. +daysUntilClose: 14 + +# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) +onlyLabels: [] + +# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable +exemptLabels: + - "Priority: P0" + - "Priority: P1" + - "Priority: P2" + - "Progress: dev in progress" + - "Progress: PR in progress" + - "Progress: done" + - "B2B: GraphQL" + - "Progress: PR Created" + - "PAP" + - "Project: Login as Customer" + - "Project: GraphQL" + +# Set to true to ignore issues in a project (defaults to false) +exemptProjects: false + +# Set to true to ignore issues in a milestone (defaults to false) +exemptMilestones: false + +# Set to true to ignore issues with an assignee (defaults to false) +exemptAssignees: false + +# Label to use when marking as stale +staleLabel: "stale issue" + +# Comment to post when marking as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed after 14 days if no further activity occurs. Thank you + for your contributions. +# Comment to post when removing the stale label. +# unmarkComment: > +# Your comment here. + +# Comment to post when closing a stale Issue or Pull Request. +# closeComment: > +# Your comment here. + +# Limit the number of actions per hour, from 1-30. Default is 30 +limitPerRun: 30 + +# Limit to only `issues` or `pulls` +only: issues + +# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': +# pulls: +# daysUntilStale: 30 +# markComment: > +# This pull request has been automatically marked as stale because it has not had +# recent activity. It will be closed if no further activity occurs. Thank you +# for your contributions. + +# issues: +# exemptLabels: +# - confirmed diff --git a/app/code/Magento/AsynchronousOperations/etc/db_schema.xml b/app/code/Magento/AsynchronousOperations/etc/db_schema.xml index 5d49d71ee46b0..ab482d2e2c761 100644 --- a/app/code/Magento/AsynchronousOperations/etc/db_schema.xml +++ b/app/code/Magento/AsynchronousOperations/etc/db_schema.xml @@ -34,7 +34,7 @@ - mergeDataProviderPrototype; $categoryId = $category->getId(); foreach ($category->getStoreIds() as $storeId) { - $category->setStoreId($storeId); if (!$this->isGlobalScope($storeId) && $this->isOverrideUrlsForStore($storeId, $categoryId, $overrideStoreUrls) ) { + $category = clone $category; // prevent undesired side effects on original object + $category->setStoreId($storeId); $this->updateCategoryUrlForStore($storeId, $category); $mergeDataProvider->merge($this->generateForSpecificStoreView($storeId, $category, $rootCategoryId)); } diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryUrlRewriteGeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryUrlRewriteGeneratorTest.php index 62a9699b3988d..1626cca6b7ae7 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryUrlRewriteGeneratorTest.php +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryUrlRewriteGeneratorTest.php @@ -158,6 +158,7 @@ public function testGenerationForGlobalScope() ], $this->categoryUrlRewriteGenerator->generate($this->category, false, $categoryId) ); + $this->assertEquals(0, $this->category->getStoreId(), 'Store ID should not have been modified'); } /** diff --git a/app/code/Magento/Checkout/view/frontend/layout/checkout_cart_sidebar_item_renderers.xml b/app/code/Magento/Checkout/view/frontend/layout/checkout_cart_sidebar_item_renderers.xml index 1b9bad3d81c65..1cf965e83718a 100644 --- a/app/code/Magento/Checkout/view/frontend/layout/checkout_cart_sidebar_item_renderers.xml +++ b/app/code/Magento/Checkout/view/frontend/layout/checkout_cart_sidebar_item_renderers.xml @@ -21,7 +21,7 @@ - uiComponent + Magento_Checkout/js/view/cart-item-renderer defaultRenderer Magento_Checkout/minicart/item/default diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/cart-item-renderer.js b/app/code/Magento/Checkout/view/frontend/web/js/view/cart-item-renderer.js new file mode 100644 index 0000000000000..e1568efc3d6c8 --- /dev/null +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/cart-item-renderer.js @@ -0,0 +1,34 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'uiComponent' +], function (Component) { + 'use strict'; + + return Component.extend({ + /** + * Prepare the product name value to be rendered as HTML + * + * @param {String} productName + * @return {String} + */ + getProductNameUnsanitizedHtml: function (productName) { + // product name has already escaped on backend + return productName; + }, + + /** + * Prepare the given option value to be rendered as HTML + * + * @param {String} optionValue + * @return {String} + */ + getOptionValueUnsanitizedHtml: function (optionValue) { + // option value has already escaped on backend + return optionValue; + } + }); +}); diff --git a/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html b/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html index 053b15b4ad343..b15b7952d2cbd 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html @@ -24,10 +24,10 @@
- + - + @@ -42,10 +42,10 @@
- + - + diff --git a/app/code/Magento/Customer/view/frontend/web/js/customer-data.js b/app/code/Magento/Customer/view/frontend/web/js/customer-data.js index 5321dfecba182..d50541504662d 100644 --- a/app/code/Magento/Customer/view/frontend/web/js/customer-data.js +++ b/app/code/Magento/Customer/view/frontend/web/js/customer-data.js @@ -78,7 +78,7 @@ define([ var parameters; sectionNames = sectionConfig.filterClientSideSections(sectionNames); - parameters = _.isArray(sectionNames) ? { + parameters = _.isArray(sectionNames) && sectionNames.indexOf('*') < 0 ? { sections: sectionNames.join(',') } : []; parameters['force_new_section_timestamp'] = forceNewSectionTimestamp; diff --git a/app/code/Magento/Directory/etc/zip_codes.xml b/app/code/Magento/Directory/etc/zip_codes.xml index 3c540f7ce0ffd..14d250656d28c 100644 --- a/app/code/Magento/Directory/etc/zip_codes.xml +++ b/app/code/Magento/Directory/etc/zip_codes.xml @@ -64,7 +64,7 @@ - ^[0-9]{5}$ + ^[0-9]{8}$ ^[0-9]{5}\-[0-9]{3}$ diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiUsedInCategoryFilterTest.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiUsedInCategoryFilterTest.xml index a495e2ff07e6a..df3ac35c0bfcd 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiUsedInCategoryFilterTest.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiUsedInCategoryFilterTest.xml @@ -27,7 +27,7 @@ - + @@ -37,6 +37,8 @@ + + @@ -50,6 +52,8 @@ + + diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyUsedInLinkProductGridTest.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyUsedInLinkProductGridTest.xml index db7942d4c53bf..d2e8574ef7033 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyUsedInLinkProductGridTest.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyUsedInLinkProductGridTest.xml @@ -29,6 +29,8 @@ + + @@ -42,6 +44,10 @@ + + + + @@ -51,6 +57,8 @@ + + @@ -64,15 +72,14 @@ - + - + - diff --git a/app/code/Magento/MediaGalleryRenditions/Model/Config.php b/app/code/Magento/MediaGalleryRenditions/Model/Config.php index d1a48904d1f13..6622ef36dffd7 100644 --- a/app/code/Magento/MediaGalleryRenditions/Model/Config.php +++ b/app/code/Magento/MediaGalleryRenditions/Model/Config.php @@ -18,7 +18,8 @@ class Config { private const TABLE_CORE_CONFIG_DATA = 'core_config_data'; - private const XML_PATH_ENABLED = 'system/media_gallery/enabled'; + private const XML_PATH_MEDIA_GALLERY_ENABLED = 'system/media_gallery/enabled'; + private const XML_PATH_ENABLED = 'system/media_gallery_renditions/enabled'; private const XML_PATH_MEDIA_GALLERY_RENDITIONS_WIDTH_PATH = 'system/media_gallery_renditions/width'; private const XML_PATH_MEDIA_GALLERY_RENDITIONS_HEIGHT_PATH = 'system/media_gallery_renditions/height'; @@ -49,6 +50,16 @@ public function __construct( * * @return bool */ + public function isMediaGalleryEnabled(): bool + { + return $this->scopeConfig->isSetFlag(self::XML_PATH_MEDIA_GALLERY_ENABLED); + } + + /** + * Should the renditions be inserted in the content instead of original image + * + * @return bool + */ public function isEnabled(): bool { return $this->scopeConfig->isSetFlag(self::XML_PATH_ENABLED); diff --git a/app/code/Magento/MediaGalleryRenditions/Plugin/SetRenditionPath.php b/app/code/Magento/MediaGalleryRenditions/Plugin/SetRenditionPath.php index ec2012c528ef1..2fc49950463ea 100644 --- a/app/code/Magento/MediaGalleryRenditions/Plugin/SetRenditionPath.php +++ b/app/code/Magento/MediaGalleryRenditions/Plugin/SetRenditionPath.php @@ -91,7 +91,7 @@ public function beforeExecute( $storeId ]; - if (!$this->config->isEnabled()) { + if (!$this->config->isEnabled() || !$this->config->isMediaGalleryEnabled()) { return $arguments; } diff --git a/app/code/Magento/MediaGalleryRenditions/Plugin/UpdateRenditionsOnConfigChange.php b/app/code/Magento/MediaGalleryRenditions/Plugin/UpdateRenditionsOnConfigChange.php index 9cf969c16782f..6fcb37398f3ad 100644 --- a/app/code/Magento/MediaGalleryRenditions/Plugin/UpdateRenditionsOnConfigChange.php +++ b/app/code/Magento/MediaGalleryRenditions/Plugin/UpdateRenditionsOnConfigChange.php @@ -8,6 +8,7 @@ namespace Magento\MediaGalleryRenditions\Plugin; use Magento\Framework\App\Config\Value; +use Magento\MediaGalleryRenditions\Model\Config; use Magento\MediaGalleryRenditions\Model\Queue\ScheduleRenditionsUpdate; /** @@ -15,6 +16,7 @@ */ class UpdateRenditionsOnConfigChange { + private const XML_PATH_MEDIA_GALLERY_RENDITIONS_ENABLE_PATH = 'system/media_gallery_renditions/enabled'; private const XML_PATH_MEDIA_GALLERY_RENDITIONS_WIDTH_PATH = 'system/media_gallery_renditions/width'; private const XML_PATH_MEDIA_GALLERY_RENDITIONS_HEIGHT_PATH = 'system/media_gallery_renditions/height'; @@ -24,10 +26,17 @@ class UpdateRenditionsOnConfigChange private $scheduleRenditionsUpdate; /** + * @var Config + */ + private $config; + + /** + * @param Config $config * @param ScheduleRenditionsUpdate $scheduleRenditionsUpdate */ - public function __construct(ScheduleRenditionsUpdate $scheduleRenditionsUpdate) + public function __construct(Config $config, ScheduleRenditionsUpdate $scheduleRenditionsUpdate) { + $this->config = $config; $this->scheduleRenditionsUpdate = $scheduleRenditionsUpdate; } @@ -41,7 +50,13 @@ public function __construct(ScheduleRenditionsUpdate $scheduleRenditionsUpdate) */ public function afterSave(Value $config, Value $result): Value { - if ($this->isRenditionsValue($result) && $result->isValueChanged()) { + if ($this->isRenditionsEnabled($result)) { + $this->scheduleRenditionsUpdate->execute(); + + return $result; + } + + if ($this->config->isEnabled() && $this->isRenditionsValue($result) && $result->isValueChanged()) { $this->scheduleRenditionsUpdate->execute(); } @@ -59,4 +74,17 @@ private function isRenditionsValue(Value $value): bool return $value->getPath() === self::XML_PATH_MEDIA_GALLERY_RENDITIONS_WIDTH_PATH || $value->getPath() === self::XML_PATH_MEDIA_GALLERY_RENDITIONS_HEIGHT_PATH; } + + /** + * Determine if media gallery renditions is enabled based on configuration value + * + * @param Value $value + * @return bool + */ + private function isRenditionsEnabled(Value $value): bool + { + return $value->getPath() === self::XML_PATH_MEDIA_GALLERY_RENDITIONS_ENABLE_PATH + && $value->isValueChanged() + && $value->getValue(); + } } diff --git a/app/code/Magento/MediaGalleryRenditions/etc/adminhtml/system.xml b/app/code/Magento/MediaGalleryRenditions/etc/adminhtml/system.xml index b60a858da5f26..f36f628cb122f 100644 --- a/app/code/Magento/MediaGalleryRenditions/etc/adminhtml/system.xml +++ b/app/code/Magento/MediaGalleryRenditions/etc/adminhtml/system.xml @@ -10,14 +10,18 @@
+ + + Magento\Config\Model\Config\Source\Yesno + Resize images to improve performance and decrease the file size. When you use an image from Media Gallery on the storefront, the smaller image is generated and placed instead of the original. Changing these settings will update all generated images. - + validate-zero-or-greater validate-digits Enter the maximum width of an image in pixels. - + validate-zero-or-greater validate-digits Enter the maximum height of an image in pixels. diff --git a/app/code/Magento/MediaGalleryRenditions/etc/config.xml b/app/code/Magento/MediaGalleryRenditions/etc/config.xml index 58c5aa1f11fd2..6b4f2351b8b10 100644 --- a/app/code/Magento/MediaGalleryRenditions/etc/config.xml +++ b/app/code/Magento/MediaGalleryRenditions/etc/config.xml @@ -9,6 +9,7 @@ + 1 1000 1000 diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminMediaGalleryRenditionsEnableActionGroup.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminMediaGalleryRenditionsEnableActionGroup.xml new file mode 100644 index 0000000000000..b64247a708242 --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminMediaGalleryRenditionsEnableActionGroup.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Data/AdobeStockConfigData.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Data/AdobeStockConfigData.xml index e8f394a006104..9ac743f2f9983 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Data/AdobeStockConfigData.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Data/AdobeStockConfigData.xml @@ -15,4 +15,12 @@ system/media_gallery/enabled 0 + + system/media_gallery_renditions/enabled + 1 + + + system/media_gallery_renditions/enabled + 0 + diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminConfigSystemSection.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminConfigSystemSection.xml index b7900f6664c62..f3ab74470e4e4 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminConfigSystemSection.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminConfigSystemSection.xml @@ -11,6 +11,8 @@
+ +
diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Suite/MediaGalleryUiDisabledSuite.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Suite/MediaGalleryUiDisabledSuite.xml index 727fbde3f17b6..d5f2abe965575 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Suite/MediaGalleryUiDisabledSuite.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Suite/MediaGalleryUiDisabledSuite.xml @@ -9,6 +9,17 @@ + + + + + + + + + + + diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Suite/MediaGalleryUiSuite.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Suite/MediaGalleryUiSuite.xml index 4749fc4a885b0..e81dc807d0f48 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Suite/MediaGalleryUiSuite.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Suite/MediaGalleryUiSuite.xml @@ -15,6 +15,7 @@ + diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGalleryDeleteImageWithWarningPopupTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGalleryDeleteImageWithWarningPopupTest.xml index 6ae8ed7047434..84dd84a22d3c3 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGalleryDeleteImageWithWarningPopupTest.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGalleryDeleteImageWithWarningPopupTest.xml @@ -33,6 +33,8 @@ + + @@ -43,11 +45,13 @@ + + - + diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGalleryStoreViewCategoryFilterTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGalleryStoreViewCategoryFilterTest.xml index eceda879e5597..9c72ee4ac38fe 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGalleryStoreViewCategoryFilterTest.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGalleryStoreViewCategoryFilterTest.xml @@ -33,6 +33,8 @@ + + @@ -56,7 +58,7 @@ - + diff --git a/app/code/Magento/Theme/Model/PageLayout/Config/Builder.php b/app/code/Magento/Theme/Model/PageLayout/Config/Builder.php index 13b8aa23073ce..e528f9e88d8a4 100644 --- a/app/code/Magento/Theme/Model/PageLayout/Config/Builder.php +++ b/app/code/Magento/Theme/Model/PageLayout/Config/Builder.php @@ -5,25 +5,38 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Theme\Model\PageLayout\Config; +use Magento\Framework\App\Cache\Type\Layout; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\View\Model\PageLayout\Config\BuilderInterface; +use Magento\Framework\View\PageLayout\ConfigFactory; +use Magento\Framework\View\PageLayout\File\Collector\Aggregated; +use Magento\Theme\Model\ResourceModel\Theme\Collection; +use Magento\Theme\Model\Theme\Data; +use Magento\Framework\Serialize\SerializerInterface; + /** * Page layout config builder */ -class Builder implements \Magento\Framework\View\Model\PageLayout\Config\BuilderInterface +class Builder implements BuilderInterface { + const CACHE_KEY_LAYOUTS = 'THEME_LAYOUTS_FILES_MERGED'; + /** - * @var \Magento\Framework\View\PageLayout\ConfigFactory + * @var ConfigFactory */ protected $configFactory; /** - * @var \Magento\Framework\View\PageLayout\File\Collector\Aggregated + * @var Aggregated */ protected $fileCollector; /** - * @var \Magento\Theme\Model\ResourceModel\Theme\Collection + * @var Collection */ protected $themeCollection; @@ -33,19 +46,36 @@ class Builder implements \Magento\Framework\View\Model\PageLayout\Config\Builder private $configFiles = []; /** - * @param \Magento\Framework\View\PageLayout\ConfigFactory $configFactory - * @param \Magento\Framework\View\PageLayout\File\Collector\Aggregated $fileCollector - * @param \Magento\Theme\Model\ResourceModel\Theme\Collection $themeCollection + * @var Layout|null + */ + private $cacheModel; + /** + * @var SerializerInterface|null + */ + private $serializer; + + /** + * @param ConfigFactory $configFactory + * @param Aggregated $fileCollector + * @param Collection $themeCollection + * @param Layout|null $cacheModel + * @param SerializerInterface|null $serializer */ public function __construct( - \Magento\Framework\View\PageLayout\ConfigFactory $configFactory, - \Magento\Framework\View\PageLayout\File\Collector\Aggregated $fileCollector, - \Magento\Theme\Model\ResourceModel\Theme\Collection $themeCollection + ConfigFactory $configFactory, + Aggregated $fileCollector, + Collection $themeCollection, + ?Layout $cacheModel = null, + ?SerializerInterface $serializer = null ) { $this->configFactory = $configFactory; $this->fileCollector = $fileCollector; $this->themeCollection = $themeCollection; - $this->themeCollection->setItemObjectClass(\Magento\Theme\Model\Theme\Data::class); + $this->themeCollection->setItemObjectClass(Data::class); + $this->cacheModel = $cacheModel + ?? ObjectManager::getInstance()->get(Layout::class); + $this->serializer = $serializer + ?? ObjectManager::getInstance()->get(SerializerInterface::class); } /** @@ -57,7 +87,7 @@ public function getPageLayoutsConfig() } /** - * Retrieve configuration files. + * Retrieve configuration files. Caches merged layouts.xml XML files. * * @return array */ @@ -65,10 +95,18 @@ protected function getConfigFiles() { if (!$this->configFiles) { $configFiles = []; - foreach ($this->themeCollection->loadRegisteredThemes() as $theme) { - $configFiles[] = $this->fileCollector->getFilesContent($theme, 'layouts.xml'); + $this->configFiles = $this->cacheModel->load(self::CACHE_KEY_LAYOUTS); + if (!empty($this->configFiles)) { + //if value in cache is corrupted. + $this->configFiles = $this->serializer->unserialize($this->configFiles); + } + if (empty($this->configFiles)) { + foreach ($this->themeCollection->loadRegisteredThemes() as $theme) { + $configFiles[] = $this->fileCollector->getFilesContent($theme, 'layouts.xml'); + } + $this->configFiles = array_merge(...$configFiles); + $this->cacheModel->save($this->serializer->serialize($this->configFiles), self::CACHE_KEY_LAYOUTS); } - $this->configFiles = array_merge(...$configFiles); } return $this->configFiles; diff --git a/app/code/Magento/Theme/Test/Unit/Model/PageLayout/Config/BuilderTest.php b/app/code/Magento/Theme/Test/Unit/Model/PageLayout/Config/BuilderTest.php index d9eccdb871222..2e2117b79e5ab 100644 --- a/app/code/Magento/Theme/Test/Unit/Model/PageLayout/Config/BuilderTest.php +++ b/app/code/Magento/Theme/Test/Unit/Model/PageLayout/Config/BuilderTest.php @@ -10,8 +10,11 @@ */ namespace Magento\Theme\Test\Unit\Model\PageLayout\Config; +use Magento\Framework\App\Cache\Type\Layout; +use Magento\Framework\Serialize\SerializerInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Framework\View\PageLayout\Config; +use Magento\Framework\View\PageLayout\ConfigFactory; use Magento\Framework\View\PageLayout\File\Collector\Aggregated; use Magento\Theme\Model\PageLayout\Config\Builder; use Magento\Theme\Model\ResourceModel\Theme\Collection; @@ -27,7 +30,7 @@ class BuilderTest extends TestCase protected $builder; /** - * @var \Magento\Framework\View\PageLayout\ConfigFactory|MockObject + * @var ConfigFactory|MockObject */ protected $configFactory; @@ -41,6 +44,15 @@ class BuilderTest extends TestCase */ protected $themeCollection; + /** + * @var Layout|MockObject + */ + protected $cacheModel; + /** + * @var SerializerInterface|MockObject + */ + protected $serializer; + /** * SetUp method * @@ -48,19 +60,24 @@ class BuilderTest extends TestCase */ protected function setUp(): void { - $this->configFactory = $this->getMockBuilder(\Magento\Framework\View\PageLayout\ConfigFactory::class) + $this->configFactory = $this->getMockBuilder(ConfigFactory::class) ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); - $this->fileCollector = $this->getMockBuilder( - Aggregated::class - )->disableOriginalConstructor() + $this->fileCollector = $this->getMockBuilder(Aggregated::class) + ->disableOriginalConstructor() ->getMock(); $this->themeCollection = $this->getMockBuilder(Collection::class) ->disableOriginalConstructor() ->getMock(); + $this->cacheModel = $this->getMockBuilder(Layout::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->serializer = $this->getMockForAbstractClass(SerializerInterface::class); + $this->themeCollection->expects($this->once()) ->method('setItemObjectClass') ->with(Data::class) @@ -72,7 +89,9 @@ protected function setUp(): void [ 'configFactory' => $this->configFactory, 'fileCollector' => $this->fileCollector, - 'themeCollection' => $this->themeCollection + 'themeCollection' => $this->themeCollection, + 'cacheModel' => $this->cacheModel, + 'serializer' => $this->serializer, ] ); } @@ -84,8 +103,10 @@ protected function setUp(): void */ public function testGetPageLayoutsConfig() { + $this->cacheModel->clean(); $files1 = ['content layouts_1.xml', 'content layouts_2.xml']; $files2 = ['content layouts_3.xml', 'content layouts_4.xml']; + $configFiles = array_merge($files1, $files2); $theme1 = $this->getMockBuilder(Data::class) ->disableOriginalConstructor() @@ -113,9 +134,17 @@ public function testGetPageLayoutsConfig() $this->configFactory->expects($this->once()) ->method('create') - ->with(['configFiles' => array_merge($files1, $files2)]) + ->with(['configFiles' => $configFiles]) ->willReturn($config); + $this->serializer->expects($this->once()) + ->method('serialize') + ->with($configFiles); + + $this->cacheModel->expects($this->once()) + ->method('save') + ->willReturnSelf(); + $this->assertSame($config, $this->builder->getPageLayoutsConfig()); } } diff --git a/app/code/Magento/Ups/Plugin/Block/DataProviders/Tracking/ChangeTitle.php b/app/code/Magento/Ups/Plugin/Block/DataProviders/Tracking/ChangeTitle.php new file mode 100644 index 0000000000000..973b199217271 --- /dev/null +++ b/app/code/Magento/Ups/Plugin/Block/DataProviders/Tracking/ChangeTitle.php @@ -0,0 +1,34 @@ +getCarrier() === Carrier::CODE) { + $result = __('Status Updated On:'); + } + return $result; + } +} diff --git a/app/code/Magento/Ups/Test/Unit/Plugin/Block/DataProviders/Tracking/ChangeTitleTest.php b/app/code/Magento/Ups/Test/Unit/Plugin/Block/DataProviders/Tracking/ChangeTitleTest.php new file mode 100644 index 0000000000000..fa608584be964 --- /dev/null +++ b/app/code/Magento/Ups/Test/Unit/Plugin/Block/DataProviders/Tracking/ChangeTitleTest.php @@ -0,0 +1,81 @@ +plugin = $objectManagerHelper->getObject(ChangeTitle::class); + } + + /** + * Check if DeliveryDateTitle was changed if the carrier is UPS + * + * @param string $carrierCode + * @param string $originalResult + * @param Phrase|string $finalResult + * @dataProvider testAfterGetTitleDataProvider + */ + public function testAfterGetTitle(string $carrierCode, string $originalResult, $finalResult) + { + /** @var DeliveryDateTitle|MockObject $subjectMock */ + $subjectMock = $this->getMockBuilder(DeliveryDateTitle::class) + ->disableOriginalConstructor() + ->getMock(); + + /** @var Status|MockObject $trackingStatusMock */ + $trackingStatusMock = $this->getMockBuilder(Status::class) + ->disableOriginalConstructor() + ->setMethods(['getCarrier']) + ->getMock(); + $trackingStatusMock->expects($this::once()) + ->method('getCarrier') + ->willReturn($carrierCode); + + $actual = $this->plugin->afterGetTitle($subjectMock, $originalResult, $trackingStatusMock); + + $this->assertEquals($finalResult, $actual); + } + + /** + * Data provider + * + * @return array + */ + public function testAfterGetTitleDataProvider(): array + { + return [ + [Carrier::CODE, 'Original Title', __('Status Updated On:')], + ['not-fedex', 'Original Title', 'Original Title'], + ]; + } +} diff --git a/app/code/Magento/Ups/etc/di.xml b/app/code/Magento/Ups/etc/di.xml index a04a5eb48bdab..08d751fc3e2c8 100644 --- a/app/code/Magento/Ups/etc/di.xml +++ b/app/code/Magento/Ups/etc/di.xml @@ -28,4 +28,7 @@ + + + diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Attribute/Backend/ConsumerTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Attribute/Backend/ConsumerTest.php new file mode 100644 index 0000000000000..8dffcdbdd4582 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Attribute/Backend/ConsumerTest.php @@ -0,0 +1,163 @@ +objectManager = Bootstrap::getObjectManager(); + $this->publisherMock = $this->getMockForAbstractClass(BulkPublisherInterface::class); + + $this->bulkManagement = $this->objectManager->create( + BulkManagement::class, + [ + 'publisher' => $this->publisherMock + ] + ); + $this->bulkStatus = $this->objectManager->get(BulkStatus::class); + $catalogProductMock = $this->createMock(Product::class); + $productFlatIndexerProcessorMock = $this->createMock( + FlatProcessor::class + ); + $productPriceIndexerProcessorMock = $this->createMock( + PriceProcessor::class + ); + $operationManagementMock = $this->createMock( + OperationManagementInterface::class + ); + $actionMock = $this->createMock(Action::class); + $loggerMock = $this->createMock(LoggerInterface::class); + $this->serializer = $this->objectManager->get(SerializerInterface::class); + $entityManager = $this->objectManager->get(EntityManager::class); + $this->model = $this->objectManager->create( + Consumer::class, + [ + 'catalogProduct' => $catalogProductMock, + 'productFlatIndexerProcessor' => $productFlatIndexerProcessorMock, + 'productPriceIndexerProcessor' => $productPriceIndexerProcessorMock, + 'operationManagement' => $operationManagementMock, + 'action' => $actionMock, + 'logger' => $loggerMock, + 'serializer' => $this->serializer, + 'entityManager' => $entityManager + ] + ); + + parent::setUp(); + } + + /** + * Testing saving bulk operation during processing operation by attribute backend consumer + */ + public function testSaveOperationDuringProcess() + { + $operation = $this->prepareUpdateAttributesBulkAndOperation(); + try { + $this->model->process($operation); + } catch (\Exception $e) { + $this->fail(sprintf('Operation save process failed.: %s', $e->getMessage())); + } + $operationStatus = $operation->getStatus(); + $this->assertEquals( + 1, + $this->bulkStatus->getOperationsCountByBulkIdAndStatus(self::BULK_UUID, $operationStatus) + ); + } + + /** + * Schedules test bulk and returns operation + * @return OperationInterface + */ + private function prepareUpdateAttributesBulkAndOperation(): OperationInterface + { + // general bulk information + $bulkUuid = self::BULK_UUID; + $bulkDescription = 'Update attributes for 2 selected products'; + $topicName = 'product_action_attribute.update'; + $userId = 1; + /** @var OperationInterfaceFactory $operationFactory */ + $operationFactory = $this->objectManager->get(OperationInterfaceFactory::class); + $operation = $operationFactory->create(); + $operation->setBulkUuid($bulkUuid) + ->setTopicName($topicName) + ->setSerializedData($this->serializer->serialize( + ['product_ids' => [1,3], 'attributes' => [], 'store_id' => '0'] + )); + $this->bulkManagement->scheduleBulk($bulkUuid, [$operation], $bulkDescription, $userId); + return $operation; + } + + /** + * Clear created bulk and operation + */ + protected function tearDown(): void + { + $this->bulkManagement->deleteBulk(self::BULK_UUID); + parent::tearDown(); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Directory/Model/Country/Postcode/ValidatorTest.php b/dev/tests/integration/testsuite/Magento/Directory/Model/Country/Postcode/ValidatorTest.php index 74db33398f721..2a4efd766b7c6 100644 --- a/dev/tests/integration/testsuite/Magento/Directory/Model/Country/Postcode/ValidatorTest.php +++ b/dev/tests/integration/testsuite/Magento/Directory/Model/Country/Postcode/ValidatorTest.php @@ -104,7 +104,7 @@ public function getPostcodesDataProvider() ['countryId' => 'BY', 'postcode' => '123456'], ['countryId' => 'BE', 'postcode' => '1234'], ['countryId' => 'BA', 'postcode' => '12345'], - ['countryId' => 'BR', 'postcode' => '12345'], + ['countryId' => 'BR', 'postcode' => '12345678'], ['countryId' => 'BR', 'postcode' => '12345-678'], ['countryId' => 'BN', 'postcode' => 'PS1234'], ['countryId' => 'BG', 'postcode' => '1234'], diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Customer/frontend/js/customer-data.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Customer/frontend/js/customer-data.test.js index 7063b846ed166..ae7c03dfd7792 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Customer/frontend/js/customer-data.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Customer/frontend/js/customer-data.test.js @@ -3,30 +3,35 @@ * See COPYING.txt for license details. */ -/*eslint-disable max-nested-callbacks*/ -/*jscs:disable jsDoc*/ +/* global _ */ +/* eslint max-nested-callbacks: 0 */ +/* jscs:disable jsDoc*/ define([ + 'squire', 'jquery', - 'underscore', 'Magento_Customer/js/section-config', - 'Magento_Customer/js/customer-data' -], function ( - $, - _, - sectionConfig, - customerData -) { + 'Magento_Customer/js/customer-data', + 'jquery/jquery-storageapi' +], function (Squire, $, sectionConfig, customerData) { 'use strict'; - var sectionConfigSettings = { + var injector = new Squire(), + obj, + originaljQuery, + originalGetJSON, + originalReload, + originalIsEmpty, + originalEach, + cookieLifeTime = 3600, + sectionConfigSettings = { baseUrls: [ 'http://localhost/' ], sections: { 'customer/account/loginpost': ['*'], 'checkout/cart/add': ['cart'], - 'rest/*/v1/guest-carts/*/selected-payment-method': ['cart','checkout-data'], + 'rest/*/v1/guest-carts/*/selected-payment-method': ['cart', 'checkout-data'], '*': ['messages'] }, clientSideSections: [ @@ -39,9 +44,7 @@ define([ 'cart', 'messages' ] - }, - cookieLifeTime = 3600, - jQueryGetJSON; + }; function init(config) { var defaultConfig = { @@ -95,75 +98,102 @@ define([ } describe('Magento_Customer/js/customer-data', function () { + + var _; + beforeAll(function () { clearLocalStorage(); }); - beforeEach(function () { - jQueryGetJSON = $.getJSON; + beforeEach(function (done) { + originalGetJSON = $.getJSON; sectionConfig['Magento_Customer/js/section-config'](sectionConfigSettings); + + injector.require([ + 'underscore', + 'Magento_Customer/js/customer-data' + ], function (underscore, Constr) { + _ = underscore; + obj = Constr; + done(); + }); }); afterEach(function () { - $.getJSON = jQueryGetJSON; - clearLocalStorage(); + try { + $.getJSON = originalGetJSON; + clearLocalStorage(); + injector.clean(); + injector.remove(); + } catch (e) { + } }); - describe('getExpiredSectionNames()', function () { - it('check that result contains expired section names', function () { - setupLocalStorage({ - 'cart': { - 'data_id': Math.floor(Date.now() / 1000) - 61 * 60, // 61 minutes ago - 'content': {} - } - }); - init(); - expect(customerData.getExpiredSectionNames()).toEqual(['cart']); + describe('"init" method', function () { + var storageInvalidation = { + keys: function () { + return ['section']; + } + }; + + beforeEach(function () { + originalReload = obj.reload; + originalIsEmpty = _.isEmpty; + + $.initNamespaceStorage('mage-cache-storage').localStorage; + $.initNamespaceStorage('mage-cache-storage-section-invalidation').localStorage; + + spyOn(storageInvalidation, 'keys').and.returnValue(['section']); }); - it('check that result doest not contain unexpired section names', function () { - setupLocalStorage({ - 'cart': { - 'data_id': Math.floor(Date.now() / 1000) + 60, // in 1 minute - 'content': {} - } - }); - init(); - expect(customerData.getExpiredSectionNames()).toEqual([]); + afterEach(function () { + obj.reload = originalReload; + _.isEmpty = originalIsEmpty; + $.namespaceStorages = {}; }); - it('check that result contains invalidated section names', function () { - setupLocalStorage({ - 'cart': { // without storage content - 'data_id': Math.floor(Date.now() / 1000) + 60 // in 1 minute - } - }); + it('Should be defined', function () { + expect(obj.hasOwnProperty('init')).toBeDefined(); + }); - init(); - expect(customerData.getExpiredSectionNames()).toEqual(['cart']); + it('Does not throw before component is initialized', function () { + obj.initStorage(); + + expect(function () { + obj.init(); + }).not.toThrow(); }); - it('check that result does not contain unsupported section names', function () { - setupLocalStorage({ - 'catalog': { // without storage content - 'data_id': Math.floor(Date.now() / 1000) + 60 // in 1 minute - } - }); + it('Calls "getExpiredSectionNames" method', function () { + spyOn(obj, 'getExpiredSectionNames').and.returnValue([]); + obj.init(); + expect(obj.getExpiredSectionNames).toHaveBeenCalled(); + }); - init(); - expect(customerData.getExpiredSectionNames()).toEqual([]); + it('Calls "reload" method when expired sections exist', function () { + spyOn(obj, 'getExpiredSectionNames').and.returnValue(['section']); + spyOn(obj, 'reload').and.returnValue(true); + obj.init(); + expect(obj.reload).toHaveBeenCalled(); }); - }); - describe('init()', function () { - it('check that sections are not requested from server, if there are no expired sections', function () { + it('Calls "reload" method when expired sections do not exist', function () { + spyOn(obj, 'getExpiredSectionNames').and.returnValue([]); + spyOn(obj, 'reload').and.returnValue(true); + spyOn(_, 'isEmpty').and.returnValue(false); + + obj.init(); + expect(obj.reload).toHaveBeenCalled(); + }); + + it('Check it does not request sections from the server if there are no expired sections', function () { setupLocalStorage({ 'catalog': { // without storage content 'data_id': Math.floor(Date.now() / 1000) + 60 // in 1 minute } }); - $.getJSON = jasmine.createSpy('$.getJSON').and.callFake(function () { + $.getJSON = jasmine.createSpy().and.callFake(function () { var deferred = $.Deferred(); return deferred.promise(); @@ -172,7 +202,8 @@ define([ init(); expect($.getJSON).not.toHaveBeenCalled(); }); - it('check that sections are requested from server, if there are expired sections', function () { + + it('Check it requests sections from the server if there are expired sections', function () { setupLocalStorage({ 'customer': { 'data_id': Math.floor(Date.now() / 1000) + 60 // invalidated, @@ -209,5 +240,280 @@ define([ ); }); }); + + describe('"getExpiredSectionNames method', function () { + it('Should be defined', function () { + expect(obj.hasOwnProperty('getExpiredSectionNames')).toBeDefined(); + }); + + it('Does not throw before component is initialized', function () { + expect(function () { + obj.getExpiredSectionNames(); + }).not.toThrow(); + }); + + it('Check that result contains expired section names', function () { + setupLocalStorage({ + 'cart': { + 'data_id': Math.floor(Date.now() / 1000) - 61 * 60, // 61 minutes ago + 'content': {} + } + }); + + $.getJSON = jasmine.createSpy('$.getJSON').and.callFake(function () { + var deferred = $.Deferred(); + + return deferred.promise(); + }); + + init(); + expect(customerData.getExpiredSectionNames()).toEqual(['cart']); + }); + + it('Check that result does not contain unexpired section names', function () { + setupLocalStorage({ + 'cart': { + 'data_id': Math.floor(Date.now() / 1000) + 60, // in 1 minute + 'content': {} + } + }); + init(); + expect(customerData.getExpiredSectionNames()).toEqual([]); + }); + + it('Check that result contains invalidated section names', function () { + setupLocalStorage({ + 'cart': { // without storage content + 'data_id': Math.floor(Date.now() / 1000) + 60 // in 1 minute + } + }); + + $.getJSON = jasmine.createSpy('$.getJSON').and.callFake(function () { + var deferred = $.Deferred(); + + return deferred.promise(); + }); + + init(); + expect(customerData.getExpiredSectionNames()).toEqual(['cart']); + }); + + it('Check that result does not contain unsupported section names', function () { + setupLocalStorage({ + 'catalog': { // without storage content + 'data_id': Math.floor(Date.now() / 1000) + 60 // in 1 minute + } + }); + + init(); + expect(customerData.getExpiredSectionNames()).toEqual([]); + }); + }); + + describe('"get" method', function () { + it('Should be defined', function () { + expect(obj.hasOwnProperty('get')).toBeDefined(); + }); + + it('Does not throw before component is initialized', function () { + expect(function () { + obj.get(); + }).not.toThrow(); + }); + }); + + describe('"set" method', function () { + beforeEach(function () { + originalEach = _.each; + }); + + afterEach(function () { + _.each = originalEach; + }); + + it('Should be defined', function () { + expect(obj.hasOwnProperty('set')).toBeDefined(); + }); + + it('Does not throw before component is initialized', function () { + _.each = jasmine.createSpy().and.returnValue(true); + + expect(function () { + obj.set(); + }).not.toThrow(); + }); + }); + + describe('"reload" method', function () { + beforeEach(function () { + originaljQuery = $; + $ = jQuery; + + $.getJSON = jasmine.createSpy().and.callFake(function () { + var deferred = $.Deferred(); + + /** + * Mock Done Method for getJSON + * @returns object + */ + deferred.promise().done = function () { + return { + responseJSON: { + section: {} + } + }; + }; + + return deferred.promise(); + }); + }); + + afterEach(function () { + $ = originaljQuery; + }); + + it('Should be defined', function () { + expect(obj.hasOwnProperty('reload')).toBeDefined(); + }); + + it('Does not throw before component is initialized', function () { + expect(function () { + obj.reload(); + }).not.toThrow(); + }); + + it('Check it returns proper sections object when passed array with a single section name', function () { + var result; + + spyOn(sectionConfig, 'filterClientSideSections').and.returnValue(['section']); + + $.getJSON = jasmine.createSpy().and.callFake(function (url, parameters) { + var deferred = $.Deferred(); + + /** + * Mock Done Method for getJSON + * @returns object + */ + deferred.promise().done = function () { + return { + responseJSON: { + section: {} + } + }; + }; + + expect(parameters).toEqual(jasmine.objectContaining({ + sections: 'section' + })); + + return deferred.promise(); + }); + + result = obj.reload(['section'], true); + + expect(result).toEqual(jasmine.objectContaining({ + responseJSON: { + section: {} + } + })); + }); + + it('Check it returns proper sections object when passed array with multiple section names', function () { + var result; + + spyOn(sectionConfig, 'filterClientSideSections').and.returnValue(['cart,customer,messages']); + + $.getJSON = jasmine.createSpy().and.callFake(function (url, parameters) { + var deferred = $.Deferred(); + + expect(parameters).toEqual(jasmine.objectContaining({ + sections: 'cart,customer,messages' + })); + + /** + * Mock Done Method for getJSON + * @returns object + */ + deferred.promise().done = function () { + return { + responseJSON: { + cart: {}, + customer: {}, + messages: {} + } + }; + }; + + return deferred.promise(); + }); + + result = obj.reload(['cart', 'customer', 'messages'], true); + + expect(result).toEqual(jasmine.objectContaining({ + responseJSON: { + cart: {}, + customer: {}, + messages: {} + } + })); + }); + // + it('Check it returns all sections when passed wildcard string', function () { + var result; + + $.getJSON = jasmine.createSpy().and.callFake(function (url, parameters) { + var deferred = $.Deferred(); + + expect(parameters).toEqual(jasmine.objectContaining({ + 'force_new_section_timestamp': true + })); + + /** + * Mock Done Method for getJSON + * @returns object + */ + deferred.promise().done = function () { + return { + responseJSON: { + cart: {}, + customer: {}, + messages: {} + } + }; + }; + + return deferred.promise(); + }); + + result = obj.reload('*', true); + + expect($.getJSON).toHaveBeenCalled(); + expect(result).toEqual(jasmine.objectContaining({ + responseJSON: { + cart: {}, + customer: {}, + messages: {} + } + })); + }); + }); + + describe('"invalidated" method', function () { + it('Should be defined', function () { + expect(obj.hasOwnProperty('invalidate')).toBeDefined(); + }); + + it('Does not throw before component is initialized', function () { + expect(function () { + obj.invalidate(); + }).not.toThrow(); + }); + }); + + describe('"Magento_Customer/js/customer-data" method', function () { + it('Should be defined', function () { + expect(obj.hasOwnProperty('Magento_Customer/js/customer-data')).toBeDefined(); + }); + }); }); }); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Multishipping/frontend/js/multi-shipping.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Multishipping/frontend/js/multi-shipping.test.js index 65ee180476f3a..a8ae8ab65e378 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Multishipping/frontend/js/multi-shipping.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Multishipping/frontend/js/multi-shipping.test.js @@ -68,9 +68,11 @@ define([ var addNewAddressBtn, addressflag, canContinueBtn, - canContinueFlag; + canContinueFlag, + originalGetJSON; beforeEach(function () { + originalGetJSON = $.getJSON; addNewAddressBtn = $('