diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index b31348bdf0a..2ebf4bfb486 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -56,6 +56,7 @@ This project aims to provide a stable and secure version of Magento 1.x, with on - Update comments to reflect changes in code. - Update tests to cover new functionality and changes in code. - Update copyright notices in new files. +- Do not add return types in docblocks if type hints are used. ## UI guidelines diff --git a/app/code/core/Mage/ConfigurableSwatches/Helper/Data.php b/app/code/core/Mage/ConfigurableSwatches/Helper/Data.php index 4c812e5a917..d3003d25c76 100644 --- a/app/code/core/Mage/ConfigurableSwatches/Helper/Data.php +++ b/app/code/core/Mage/ConfigurableSwatches/Helper/Data.php @@ -84,6 +84,14 @@ public static function normalizeKey($key) return trim(strtolower($key)); } + /** + * Wraps a key in a Normalized object that normalizes to lowercase but preserves original value + */ + public static function normalizeKeyToObject(?string $key): Mage_ConfigurableSwatches_Model_String_Normalized + { + return new Mage_ConfigurableSwatches_Model_String_Normalized($key); + } + /** * Get list of attributes that should use swatches * diff --git a/app/code/core/Mage/ConfigurableSwatches/Helper/List/Price.php b/app/code/core/Mage/ConfigurableSwatches/Helper/List/Price.php index 9ae47158933..a24ea1669be 100644 --- a/app/code/core/Mage/ConfigurableSwatches/Helper/List/Price.php +++ b/app/code/core/Mage/ConfigurableSwatches/Helper/List/Price.php @@ -65,8 +65,7 @@ public function attachConfigurableProductChildrenPricesMapping(array $products, ['product' => $product], ); $configurablePrice = $product->getConfigurablePrice(); - $cofigurableSwatchesHelper = Mage::helper('configurableswatches'); - $result[$cofigurableSwatchesHelper::normalizeKey($attributePrice['store_label'])] = [ + $result[Mage_ConfigurableSwatches_Helper_Data::normalizeKey($attributePrice['store_label'])] = [ 'price' => $configurablePrice, 'oldPrice' => $this->_getHelper()->prepareOldPrice( $product, diff --git a/app/code/core/Mage/ConfigurableSwatches/Helper/Mediafallback.php b/app/code/core/Mage/ConfigurableSwatches/Helper/Mediafallback.php index acda04049dd..5fef616b8bd 100644 --- a/app/code/core/Mage/ConfigurableSwatches/Helper/Mediafallback.php +++ b/app/code/core/Mage/ConfigurableSwatches/Helper/Mediafallback.php @@ -78,7 +78,7 @@ public function attachProductChildrenAttributeMapping(array $parentProducts, $st // normalize to all lower case before we start using them $optionLabels = array_map(function ($value) { - return array_map(Mage_ConfigurableSwatches_Helper_Data::normalizeKey(...), $value); + return array_map(Mage_ConfigurableSwatches_Helper_Data::normalizeKeyToObject(...), $value); }, $optionLabels); foreach ($parentProducts as $parentProduct) { @@ -109,7 +109,8 @@ public function attachProductChildrenAttributeMapping(array $parentProducts, $st } // using default value as key unless store-specific label is present - $optionLabel = $optionLabels[$optionId][$storeId] ?? $optionLabels[$optionId][0]; + $optionLabelObject = $optionLabels[$optionId][$storeId] ?? $optionLabels[$optionId][0]; + $optionLabel = (string) $optionLabelObject; // initialize arrays if not present if (!isset($mapping[$optionLabel])) { @@ -119,7 +120,7 @@ public function attachProductChildrenAttributeMapping(array $parentProducts, $st } $mapping[$optionLabel]['product_ids'][] = $childProduct->getId(); - $mapping[$optionLabel]['label'] = $optionLabel; + $mapping[$optionLabel]['label'] = $optionLabelObject; $mapping[$optionLabel]['default_label'] = $optionLabels[$optionId][0]; $mapping[$optionLabel]['labels'] = $optionLabels[$optionId]; @@ -184,10 +185,11 @@ public function getConfigurableImagesFallbackArray( // load images from the configurable product for swapping if (is_array($mapping)) { foreach ($mapping as $map) { + $mapLabel = (string) $map['label']; $imagePath = null; //search by store-specific label and then default label if nothing is found - $imageKey = array_search($map['label'], $imageHaystack); + $imageKey = array_search($mapLabel, $imageHaystack); if ($imageKey === false) { $imageKey = array_search($map['default_label'], $imageHaystack); } @@ -197,7 +199,7 @@ public function getConfigurableImagesFallbackArray( $imagePath = $mediaGallery['images'][$imageKey]['file']; } - $imagesByLabel[$map['label']] = [ + $imagesByLabel[$mapLabel] = [ 'configurable_product' => [ Mage_ConfigurableSwatches_Helper_Productimg::MEDIA_IMAGE_TYPE_SMALL => null, Mage_ConfigurableSwatches_Helper_Productimg::MEDIA_IMAGE_TYPE_BASE => null, @@ -206,11 +208,11 @@ public function getConfigurableImagesFallbackArray( ]; if ($imagePath) { - $imagesByLabel[$map['label']]['configurable_product'] + $imagesByLabel[$mapLabel]['configurable_product'] [Mage_ConfigurableSwatches_Helper_Productimg::MEDIA_IMAGE_TYPE_SMALL] = $this->_resizeProductImage($product, 'small_image', $keepFrame, $imagePath); - $imagesByLabel[$map['label']]['configurable_product'] + $imagesByLabel[$mapLabel]['configurable_product'] [Mage_ConfigurableSwatches_Helper_Productimg::MEDIA_IMAGE_TYPE_BASE] = $this->_resizeProductImage($product, 'image', $keepFrame, $imagePath); } diff --git a/app/code/core/Mage/ConfigurableSwatches/Model/Resource/Catalog/Product/Attribute/Super/Collection.php b/app/code/core/Mage/ConfigurableSwatches/Model/Resource/Catalog/Product/Attribute/Super/Collection.php index f485132ad84..2fd3df18b3a 100644 --- a/app/code/core/Mage/ConfigurableSwatches/Model/Resource/Catalog/Product/Attribute/Super/Collection.php +++ b/app/code/core/Mage/ConfigurableSwatches/Model/Resource/Catalog/Product/Attribute/Super/Collection.php @@ -119,7 +119,9 @@ protected function _getOptionLabels() ->where( 'labels.store_id IN (?)', [Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID, $this->getStoreId()], - ); + ) + ->order('options.sort_order ASC') + ->order('labels.value ASC'); $resultSet = $this->getConnection()->query($select); $labels = []; diff --git a/app/code/core/Mage/ConfigurableSwatches/Model/String/Normalized.php b/app/code/core/Mage/ConfigurableSwatches/Model/String/Normalized.php new file mode 100644 index 00000000000..f0e91a45ac8 --- /dev/null +++ b/app/code/core/Mage/ConfigurableSwatches/Model/String/Normalized.php @@ -0,0 +1,44 @@ +originalValue = $originalValue; + } + + /** + * Get normalized string value + */ + public function __toString(): string + { + return Mage_ConfigurableSwatches_Helper_Data::normalizeKey($this->originalValue); + } + + /** + * Get non-normalized original value + */ + public function getOriginalValue(): ?string + { + return $this->originalValue; + } +} diff --git a/app/design/frontend/rwd/default/template/configurableswatches/catalog/product/list/swatches.phtml b/app/design/frontend/rwd/default/template/configurableswatches/catalog/product/list/swatches.phtml index 8ba3bcd5f2a..2241f18716a 100644 --- a/app/design/frontend/rwd/default/template/configurableswatches/catalog/product/list/swatches.phtml +++ b/app/design/frontend/rwd/default/template/configurableswatches/catalog/product/list/swatches.phtml @@ -8,8 +8,10 @@ /** @var Mage_Core_Block_Template $this */ +/** @var Mage_Catalog_Model_Product $_product */ $_product = $this->getProduct(); +/** @var Mage_ConfigurableSwatches_Model_String_Normalized[] $_attrValues */ if (Mage::helper('configurableswatches')->isEnabled() && $_product && $_product->getId() && ($_attrValues = $_product->getListSwatchAttrValues()) && count($_attrValues) > 0): $_attrStockValues = $_product->getListSwatchAttrStockValues(); @@ -25,31 +27,47 @@ if (Mage::helper('configurableswatches')->isEnabled() && $_product && $_product- $_optionLabel): ?> getHyphenatedString($_optionLabel); - $_swatchUrl = Mage::helper('configurableswatches/productimg')->getSwatchUrl($_product, $_optionLabel, $_swatchInnerWidth, $_swatchInnerHeight, $_swatchType); + $_swatchUrl = Mage::helper('configurableswatches/productimg')->getSwatchUrl( + $_product, + $_optionLabel, + $_swatchInnerWidth, + $_swatchInnerHeight, + $_swatchType + ); + + $_label = $this->escapeHtml($_optionLabel->getOriginalValue()); $_hasImage = !empty($_swatchUrl); - $_liClasses = []; + $_liClass = ''; $_aClass = 'swatch-link swatch-link-' . $_swatchAttribute->getId(); if ($_hasImage) { - if ($_swatchType == 'media') { - $_liClasses[] = 'is-media'; - } + $_liClass .= $_swatchType == 'media' ? ' is-media' : ''; $_aClass .= ' has-image'; - } elseif (strlen($_optionLabel) > 3) { - $_liClasses[] = 'wide-swatch'; + } elseif (strlen($_label) > 3) { + $_liClass .= ' wide-swatch'; } if (Mage::helper('configurableswatches/productlist')->swatchMatchesFilter($_optionValue)) { - $_liClasses[] = 'filter-match'; + $_liClass .= ' filter-match'; } - $_liClass = (!empty($_liClasses)) ? ' ' . implode(' ', $_liClasses) : ''; ?> -
  • - - +
  • + + - <?php echo $_optionLabel; ?> + <?php echo $_label ?> - + diff --git a/app/design/frontend/rwd/default/template/configurableswatches/catalog/product/view/type/options/configurable/swatches.phtml b/app/design/frontend/rwd/default/template/configurableswatches/catalog/product/view/type/options/configurable/swatches.phtml index db88e623450..429924ad402 100644 --- a/app/design/frontend/rwd/default/template/configurableswatches/catalog/product/view/type/options/configurable/swatches.phtml +++ b/app/design/frontend/rwd/default/template/configurableswatches/catalog/product/view/type/options/configurable/swatches.phtml @@ -21,7 +21,6 @@ $_swatchOuterHeight = $this->getSwatchOuterHeight(); $_attr = $_attribute->getProductAttribute(); $_attrCode = $_attr->getAttributeCode(); -/** @var string $_id */ $_id = $_attribute->getAttributeId(); $_swatchArray = $_config->attributes->$_id; @@ -41,25 +40,44 @@ $_swatchArray = $_config->attributes->$_id; options as $_option): ?> getHyphenatedString($_option->label); - $_swatchUrl = Mage::helper('configurableswatches/productimg')->getSwatchUrl($_product, $_option->label, $_swatchInnerWidth, $_swatchInnerHeight, $_swatchType); + $_swatchUrl = Mage::helper('configurableswatches/productimg')->getSwatchUrl( + $_product, + $_option->label, + $_swatchInnerWidth, + $_swatchInnerHeight, + $_swatchType + ); + + $_label = $this->escapeHtml($_option->label); $_hasImage = !empty($_swatchUrl); $_liClass = ''; $_aClass = 'swatch-link swatch-link-' . $_attribute->getAttributeId(); if ($_hasImage) { $_liClass .= $_swatchType == 'media' ? ' is-media' : ''; $_aClass .= ' has-image'; - } elseif (strlen($_option->label) > 3) { + } elseif (strlen($_label) > 3) { $_liClass .= ' wide-swatch'; } ?> -
  • - - +
  • + + - <?php echo $_option->label; ?> + <?php echo $_label ?> - label; ?> + X diff --git a/cypress/e2e/openmage/frontend/catalog/category.cy.js b/cypress/e2e/openmage/frontend/catalog/category.cy.js new file mode 100644 index 00000000000..1aacfc1f618 --- /dev/null +++ b/cypress/e2e/openmage/frontend/catalog/category.cy.js @@ -0,0 +1,23 @@ +const test = cy.openmage.test.frontend.catalog.category.config; + +describe('Check catalog category page', () => { + beforeEach('Go to page', () => { + cy.visit(test.url); + }); + + it('tests swatch: color', () => { + const options = 'ul.configurable-swatch-color'; + const swatchLink = 'li a.swatch-link'; + const colors = ['Charcoal', 'Khaki', 'Red', 'Royal Blue']; + const images = ['msj006t', 'msj006c-khaki', 'msj006c-red', 'msj006c-royal-blue']; + + cy.get(options).eq(0).find(swatchLink).should('have.length', 4); + + cy.get(options).eq(0).find(swatchLink).each((swatch, index) => { + cy.wrap(swatch).invoke('attr', 'title').should('eq', colors[index]); + cy.wrap(swatch).click(); + cy.get('img.product-collection-image-404').should('have.attr', 'src').should('include', images[index]); + cy.wait(500); + }); + }); +}) diff --git a/cypress/e2e/openmage/frontend/catalog/product.cy.js b/cypress/e2e/openmage/frontend/catalog/product.cy.js new file mode 100644 index 00000000000..f07e75153a7 --- /dev/null +++ b/cypress/e2e/openmage/frontend/catalog/product.cy.js @@ -0,0 +1,36 @@ +const test = cy.openmage.test.frontend.catalog.product.config; + +describe('Check catalog product page', () => { + beforeEach('Go to page', () => { + cy.visit(test.url); + }); + + it('tests swatch: color', () => { + const options = 'ul#configurable_swatch_color'; + const swatchLink = 'li a.swatch-link'; + const colors = ['Charcoal', 'Khaki', 'Red', 'Royal Blue']; + const images = ['msj006t_4', 'msj006c-khaki', 'msj006c-red', 'msj006c-royal-blue']; + + cy.get(options).eq(0).find(swatchLink).should('have.length', 4); + + cy.get(options).eq(0).find(swatchLink).each((swatch, index) => { + cy.wrap(swatch).invoke('attr', 'title').should('eq', colors[index]); + cy.wrap(swatch).click(); + cy.get('img.gallery-image.visible').should('have.attr', 'src').should('include', images[index]); + cy.wait(500); + }); + }); + + it('tests swatch: size', () => { + const options = 'ul#configurable_swatch_size'; + const swatchLink = 'li a.swatch-link'; + const sizes = ['XS', 'S', 'M', 'L', 'XL']; + + cy.get(options).eq(0).find(swatchLink).should('have.length', 5); + + cy.get(options).eq(0).find(swatchLink).each((swatch, index) => { + cy.get(options).eq(0).contains(sizes[index]).should('exist'); + cy.wrap(swatch).invoke('attr', 'title').should('eq', sizes[index]); + }); + }); +}) diff --git a/cypress/support/e2e.js b/cypress/support/e2e.js index aabf07d5eaa..d29688b5b7c 100644 --- a/cypress/support/e2e.js +++ b/cypress/support/e2e.js @@ -61,5 +61,7 @@ import './openmage/backend/system/notification' import './openmage/backend/system/store' import './openmage/backend/system/variable' +import './openmage/frontend/catalog/category' +import './openmage/frontend/catalog/product' import './openmage/frontend/customer/account' import './openmage/frontend/homepage/newsletter' diff --git a/cypress/support/openmage/_utils/test.js b/cypress/support/openmage/_utils/test.js index 597889f4d18..891ed42ceed 100644 --- a/cypress/support/openmage/_utils/test.js +++ b/cypress/support/openmage/_utils/test.js @@ -302,6 +302,9 @@ cy.openmage.test.backend.system.variable = {}; * @type {{}} */ cy.openmage.test.frontend = {}; +cy.openmage.test.frontend.catalog = {}; +cy.openmage.test.frontend.catalog.category = {}; +cy.openmage.test.frontend.catalog.product = {}; cy.openmage.test.frontend.customer = {}; cy.openmage.test.frontend.customer.account = {}; cy.openmage.test.frontend.homepage = { diff --git a/cypress/support/openmage/frontend/catalog/category.js b/cypress/support/openmage/frontend/catalog/category.js new file mode 100644 index 00000000000..c16bcac4726 --- /dev/null +++ b/cypress/support/openmage/frontend/catalog/category.js @@ -0,0 +1,5 @@ +const test = cy.openmage.test.frontend.catalog.category; + +test.config = { + url: cy.openmage.test.frontend.homepage._url + 'men/shirts.html' +} diff --git a/cypress/support/openmage/frontend/catalog/product.js b/cypress/support/openmage/frontend/catalog/product.js new file mode 100644 index 00000000000..fbfb6d3b301 --- /dev/null +++ b/cypress/support/openmage/frontend/catalog/product.js @@ -0,0 +1,5 @@ +const test = cy.openmage.test.frontend.catalog.product; + +test.config = { + url: cy.openmage.test.frontend.homepage._url + 'men/shirts/plaid-cotton-shirt-476.html' +}