From 96f69fc083716f1f7ed0f449d1dc09edaf9d6bb5 Mon Sep 17 00:00:00 2001 From: Aanchal Pawar <97873570+glo82145@users.noreply.github.com> Date: Fri, 8 Aug 2025 23:20:44 +0530 Subject: [PATCH 01/36] Sync main 14.3.1 (#4533) * v14.3.1-alpha1 * Merge pull request #4525 from magento/patch/npm-access-update-live-search Adding Public Access to publish to NPM * v14.3.1-alpha12 * v14.3.1-beta1 * PWA-14.3.1 release notes (#4530) * PWA-14.3.1 release notes (#4531) * Update magento-compatibility.js (#4532) * v14.3.1 --------- Co-authored-by: devops-pwa-codebuild Co-authored-by: Ilesh Tiwari --- CHANGELOG.md | 83 +++++++------------ magento-compatibility.js | 1 + package.json | 2 +- packages/create-pwa/package.json | 2 +- .../package.json | 2 +- .../venia-pwa-live-search/package.json | 3 + .../venia-sample-eventing/package.json | 2 +- .../package.json | 2 +- .../package.json | 2 +- packages/pagebuilder/package.json | 6 +- packages/peregrine/package.json | 2 +- packages/venia-concept/package.json | 6 +- packages/venia-ui/package.json | 2 +- pwa-devdocs/package.json | 2 +- 14 files changed, 49 insertions(+), 68 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34556d6eab..63d98f08ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,37 +1,23 @@ -# PWA Studio Release 14.3.0 +# PWA Studio Release 14.3.1 **NOTE:** -_This changelog only contains release notes for PWA Studio and Venia 14.3.0_ +_This changelog only contains release notes for PWA Studio and Venia 14.3.1_ _For older release notes, see_ [PWA Studio releases][]. ## Highlights -PHP 8.4 support — PWA Studio now supports PHP 8.4. +The 14.3.1 release of PWA Studio provides newly built package PWA Live Search -The 14.3.0 release of PWA Studio provides compatibility with PREX extension and Upgradation from node 18 to 20. - -Product Recommendation module is now part of PWA extensions - -Plugin braintree three d secure is released ## Additional fixes -- Story: [4463][] — plugin braintree three d secure -- Story: [4456][] — PWA multistore setup -- Story: [4458][] — Prex is compatible with PWA -- Bug: [4454][] — Updated Docker changes to fix UI issue on PR link -- Bug: [4384][] — Removed unused Option in Select -- Bug: [27][] — Made 3rd party vendor Mustache is compatible with PHP 8.4 -- Bug: [4472][] — Wishlist page if we click on product image or product link is now redirecting to PDP page. -- Bug: [4466][] - Failing PR test cases has been fixed -- Bug: [4506][] — Resolved internal server error in Order History page when we click on order Search button when search field is empty -- Bug: [4508][] — Post Scafhold all commands are failing in when scafholded using "yarn create @magento/pwa" command is resolved -- Bug: [70][] — When we try to create account getting error is resolved +- Story: [4524][] — Live search is built + -## 14.3.0 Lighthouse scores +## 14.3.1 Lighthouse scores -With each new release of PWA Studio, we perform Lighthouse audits of four Venia page types, each representing a different level of complexity. Shown below are the Lighthouse scores for the 14.3.0 release of these pages on desktop and mobile devices. +With each new release of PWA Studio, we perform Lighthouse audits of four Venia page types, each representing a different level of complexity. Shown below are the Lighthouse scores for the 14.3.1 release of these pages on desktop and mobile devices. ### Desktop scores @@ -63,7 +49,7 @@ When a user logs out, that user's local storage session persists. As a result, t ## Upgrading from a previous version -Use the steps outlined in this section to update your [scaffolded project][] from 14.2.0 to 14.3.0 +Use the steps outlined in this section to update your [scaffolded project][] from 14.3.0 to 14.3.1 See [Upgrading versions][] for more information about upgrading between PWA Studio versions. [scaffolded project]: https://developer.adobe.com/commerce/pwa-studio/tutorials/ @@ -72,7 +58,7 @@ See [Upgrading versions][] for more information about upgrading between PWA Stud ### Updated package dependencies Open your `package.json` file and update the PWA Studio package dependencies to the versions associated with this release. -The following table lists the latest versions of each package as of 14.3.0. The **bolded** versions with an asterisk (*) are the packages that were updated from PWA Studio 14.2.0. +The following table lists the latest versions of each package as of 14.3.1. The **bolded** versions with an asterisk (*) are the packages that were updated from PWA Studio 14.3.0. **NOTE:** Your project may not depend on some packages listed in this table. @@ -80,42 +66,33 @@ Your project may not depend on some packages listed in this table. | Package | Latest version | |----------------------------------------|----------------| | `babel-preset-peregrine` | 1.3.3 | -| `create-pwa` | **2.5.7*** | -| `experience-platform-connector` | **1.0.10*** | -| `upward-security-headers` | **1.0.18*** | +| `create-pwa` | 2.5.7 | +| `experience-platform-connector` | 1.0.10 | +| `upward-security-headers` | 1.0.18 | | `venia-sample-backends` | 0.0.12 | -| `venia-sample-eventing` | **0.0.11*** | -| `venia-sample-language-packs` | **0.0.19*** | -| `venia-sample-payments-checkmo` | **0.0.17*** | -| `venia-sample-payments-cashondelivery` | **0.0.2*** | -| `venia-product-recommendations` | **1.0.2*** | -| `plugin-braintree-three-d-secure` | **1.0.0*** | -| `pagebuilder` | **9.3.4*** | -| `peregrine` | **15.5.1*** | +| `venia-sample-eventing` | 0.0.11 | +| `venia-sample-language-packs` | 0.0.19 | +| `venia-sample-payments-checkmo` | 0.0.17 | +| `venia-sample-payments-cashondelivery` | 0.0.2 | +| `venia-product-recommendations` | 1.0.2 | +| `plugin-braintree-three-d-secure` | 1.0.0 | +| `venia_pwa_live_search:` | 1.0.0 | +| `pagebuilder` | **9.3.5*** | +| `peregrine` | **15.5.2*** | | `pwa-buildpack` | 11.5.4 | | `pwa-theme-venia` | 2.4.0 | | `upward-js` | 5.4.2 | | `upward-spec` | 5.3.1 | -| `venia-concept` | **14.3.0*** | -| `venia-ui` | **11.7.0*** | -| `magento2-pwa` | **0.10.2*** | -| `magento2-pwa-commerce` | **0.1.5*** | +| `venia-concept` | **14.3.1*** | +| `venia-ui` | 11.7.0 | +| `magento2-pwa` | 0.10.2 | +| `magento2-pwa-commerce` | 0.1.5 | | `magento-venia-sample-data-modules` | 0.0.6 | -| `magento-venia-sample-data-modules-ee` | **0.0.6*** | -| `magento2-upward-connector` | **2.1.5*** | -| `upward-php` | **2.1.4*** | - -[4454]: https://github.com/magento/pwa-studio/pull/4454 -[4384]: https://github.com/magento/pwa-studio/pull/4384 -[27]: https://github.com/magento-commerce/magento2-upward-connector/pull/27 -[4466]: https://github.com/magento/pwa-studio/pull/4466 -[4472]: https://github.com/magento/pwa-studio/pull/4472 -[4463]: https://github.com/magento/pwa-studio/pull/4463 -[4506]: https://github.com/magento/pwa-studio/pull/4506 -[4508]: https://github.com/magento/pwa-studio/pull/4508 -[70]: https://github.com/magento-commerce/magento2-pwa/pull/70 -[4456]: https://github.com/magento/pwa-studio/pull/4456 -[4458]: https://github.com/magento/pwa-studio/pull/4458 +| `magento-venia-sample-data-modules-ee` | 0.0.6 | +| `magento2-upward-connector` | 2.1.5 | +| `upward-php` | 2.1.4 | +| `pwa-live-search` | 1.0.0 | +[4524]: https://github.com/magento/pwa-studio/pull/4524 [PWA Studio releases]: https://github.com/magento/pwa-studio/releases diff --git a/magento-compatibility.js b/magento-compatibility.js index 41dbbbf435..4519af7f1a 100644 --- a/magento-compatibility.js +++ b/magento-compatibility.js @@ -4,6 +4,7 @@ // PWA Studio version -> Magento version. module.exports = { + '14.3.1': '2.4.8', '14.3.0': '2.4.8', '14.2.0': '2.4.7-p4', '14.1.0': '2.4.7-p2', diff --git a/package.json b/package.json index 672713b8c9..cfed845a4f 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@magento/pwa-studio", - "version": "14.3.0", + "version": "14.3.1", "private": true, "workspaces": [ "packages/babel-preset-peregrine", diff --git a/packages/create-pwa/package.json b/packages/create-pwa/package.json index 0ae5e18e13..74b4c22b5c 100644 --- a/packages/create-pwa/package.json +++ b/packages/create-pwa/package.json @@ -30,7 +30,7 @@ "homepage": "https://github.com/magento/pwa-studio/tree/main/packages/create-pwa#readme", "dependencies": { "@magento/pwa-buildpack": "~11.5.4", - "@magento/venia-concept": "~14.3.0", + "@magento/venia-concept": "~14.3.1", "chalk": "^2.4.2", "change-case": "^3.1.0", "compression": "~1.7.4", diff --git a/packages/extensions/experience-platform-connector/package.json b/packages/extensions/experience-platform-connector/package.json index 724c511d68..7fbd6c62ee 100644 --- a/packages/extensions/experience-platform-connector/package.json +++ b/packages/extensions/experience-platform-connector/package.json @@ -18,7 +18,7 @@ "devDependencies": {}, "peerDependencies": { "@apollo/client": "~3.5.0", - "@magento/peregrine": "~15.5.1", + "@magento/peregrine": "~15.5.2", "@magento/pwa-buildpack": "~11.5.4", "react": "~17.0.1" }, diff --git a/packages/extensions/venia-pwa-live-search/package.json b/packages/extensions/venia-pwa-live-search/package.json index 2915a6c6cc..dc4d6a39e3 100644 --- a/packages/extensions/venia-pwa-live-search/package.json +++ b/packages/extensions/venia-pwa-live-search/package.json @@ -1,6 +1,9 @@ { "name": "@magento/venia-pwa-live-search", "version": "1.0.0", + "publishConfig": { + "access": "public" + }, "description": "Live Search PWA Studio extension.", "main": "src/index.jsx", "scripts": { diff --git a/packages/extensions/venia-sample-eventing/package.json b/packages/extensions/venia-sample-eventing/package.json index 4c44cb1244..9a5433ca93 100644 --- a/packages/extensions/venia-sample-eventing/package.json +++ b/packages/extensions/venia-sample-eventing/package.json @@ -12,7 +12,7 @@ "repository": "github:magento/pwa-studio", "license": "(OSL-3.0 OR AFL-3.0)", "peerDependencies": { - "@magento/peregrine": "~15.5.1", + "@magento/peregrine": "~15.5.2", "@magento/pwa-buildpack": "~11.5.4", "react": "~17.0.1" }, diff --git a/packages/extensions/venia-sample-payments-cashondelivery/package.json b/packages/extensions/venia-sample-payments-cashondelivery/package.json index 5b0ce4ea6f..48cb9c56ab 100644 --- a/packages/extensions/venia-sample-payments-cashondelivery/package.json +++ b/packages/extensions/venia-sample-payments-cashondelivery/package.json @@ -12,7 +12,7 @@ "repository": "github:magento/pwa-studio", "license": "(OSL-3.0 OR AFL-3.0)", "peerDependencies": { - "@magento/peregrine": "~15.5.1", + "@magento/peregrine": "~15.5.2", "@magento/pwa-buildpack": "~11.5.4", "@magento/venia-ui": "~11.7.0", "react": "~17.0.1", diff --git a/packages/extensions/venia-sample-payments-checkmo/package.json b/packages/extensions/venia-sample-payments-checkmo/package.json index 3663a20e03..5dc41f10ae 100644 --- a/packages/extensions/venia-sample-payments-checkmo/package.json +++ b/packages/extensions/venia-sample-payments-checkmo/package.json @@ -12,7 +12,7 @@ "repository": "github:magento/pwa-studio", "license": "(OSL-3.0 OR AFL-3.0)", "peerDependencies": { - "@magento/peregrine": "~15.5.1", + "@magento/peregrine": "~15.5.2", "@magento/pwa-buildpack": "~11.5.4", "@magento/venia-ui": "~11.7.0", "react": "~17.0.1", diff --git a/packages/pagebuilder/package.json b/packages/pagebuilder/package.json index 6fb365b18e..1512e8d0ed 100644 --- a/packages/pagebuilder/package.json +++ b/packages/pagebuilder/package.json @@ -1,6 +1,6 @@ { "name": "@magento/pagebuilder", - "version": "9.3.4", + "version": "9.3.5", "publishConfig": { "access": "public" }, @@ -34,7 +34,7 @@ "homepage": "https://github.com/magento/pwa-studio/tree/main/packages/pagebuilder#readme", "dependencies": {}, "devDependencies": { - "@magento/peregrine": "~15.5.1", + "@magento/peregrine": "~15.5.2", "@magento/pwa-buildpack": "~11.5.4", "@magento/venia-ui": "~11.7.0", "@storybook/react": "~6.3.7", @@ -50,7 +50,7 @@ "peerDependencies": { "@apollo/client": "~3.5.0", "@magento/babel-preset-peregrine": "~1.3.3", - "@magento/peregrine": "~15.5.1", + "@magento/peregrine": "~15.5.2", "@magento/pwa-buildpack": "~11.5.4", "@magento/venia-ui": "~11.7.0", "jarallax": "~1.11.1", diff --git a/packages/peregrine/package.json b/packages/peregrine/package.json index 6dc133f9bd..5ea55ba85f 100644 --- a/packages/peregrine/package.json +++ b/packages/peregrine/package.json @@ -1,6 +1,6 @@ { "name": "@magento/peregrine", - "version": "15.5.1", + "version": "15.5.2", "publishConfig": { "access": "public" }, diff --git a/packages/venia-concept/package.json b/packages/venia-concept/package.json index f8d161988f..f0ae3ea533 100644 --- a/packages/venia-concept/package.json +++ b/packages/venia-concept/package.json @@ -1,6 +1,6 @@ { "name": "@magento/venia-concept", - "version": "14.3.0", + "version": "14.3.1", "publishConfig": { "access": "public" }, @@ -55,8 +55,8 @@ "@babel/runtime": "~7.15.3", "@magento/babel-preset-peregrine": "~1.3.3", "@magento/eslint-config": "~1.5.0", - "@magento/pagebuilder": "~9.3.4", - "@magento/peregrine": "~15.5.1", + "@magento/pagebuilder": "~9.3.5", + "@magento/peregrine": "~15.5.2", "@magento/pwa-theme-venia": "~2.4.0", "@magento/upward-security-headers": "~1.1.18", "@magento/venia-ui": "~11.7.0", diff --git a/packages/venia-ui/package.json b/packages/venia-ui/package.json index 6ab50699d8..f9dfedf14f 100644 --- a/packages/venia-ui/package.json +++ b/packages/venia-ui/package.json @@ -80,7 +80,7 @@ "peerDependencies": { "@apollo/client": "~3.5.0", "@magento/babel-preset-peregrine": "~1.3.3", - "@magento/peregrine": "~15.5.1", + "@magento/peregrine": "~15.5.2", "@magento/pwa-buildpack": "~11.5.4", "apollo-cache-persist": "~0.1.1", "braintree-web-drop-in": "~1.43.0", diff --git a/pwa-devdocs/package.json b/pwa-devdocs/package.json index 974405b93e..1544138ce8 100644 --- a/pwa-devdocs/package.json +++ b/pwa-devdocs/package.json @@ -1,7 +1,7 @@ { "name": "pwa-devdocs", "private": true, - "version": "14.3.0", + "version": "14.3.1", "description": "A documentation site for Magento PWA", "main": "gulpfile.js", "dependencies": { From 83727ee0f8b4544258aeaf73afee5d03519e3106 Mon Sep 17 00:00:00 2001 From: Bharathidasan Elangovan Date: Thu, 28 Aug 2025 17:41:17 +0530 Subject: [PATCH 02/36] PWA-3536: Removed package-lock.json (#4535) --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 803d38cc63..5b9963fcdb 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ docker/certs ## May temporarily create tarballs during scaffolding debug packages/**/*.tgz + +package-lock.json \ No newline at end of file From 2b1d48d46882e49230dbc76c7e420f322f8b23a7 Mon Sep 17 00:00:00 2001 From: Ilesh Tiwari Date: Thu, 28 Aug 2025 17:46:50 +0530 Subject: [PATCH 03/36] PWA-3463: Supressing noisy warnings on Select (#4505) --- packages/venia-ui/lib/components/Select/select.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/venia-ui/lib/components/Select/select.js b/packages/venia-ui/lib/components/Select/select.js index 553f03a93e..264e3e1c18 100644 --- a/packages/venia-ui/lib/components/Select/select.js +++ b/packages/venia-ui/lib/components/Select/select.js @@ -1,11 +1,7 @@ import React, { Fragment } from 'react'; import { arrayOf, node, number, oneOfType, shape, string } from 'prop-types'; -import { - Option as InformedOption, - Select as InformedSelect, - useFieldState -} from 'informed'; - +import { Option as InformedOption, Select as InformedSelect } from 'informed'; +import useFieldState from '@magento/peregrine/lib/hooks/hook-wrappers/useInformedFieldStateWrapper'; import { useStyle } from '../../classify'; import { FieldIcons, Message } from '../Field'; import defaultClasses from './select.module.css'; From 1afe1ff9301bdd7b7abe19ca3d2c279fbffb8f16 Mon Sep 17 00:00:00 2001 From: Brendan Falkowski Date: Fri, 29 Aug 2025 04:52:53 -0700 Subject: [PATCH 04/36] PWA-4468: use the same version of `react-tabs` everywhere (#4469) --- packages/pagebuilder/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pagebuilder/package.json b/packages/pagebuilder/package.json index 1512e8d0ed..bc0dedbf27 100644 --- a/packages/pagebuilder/package.json +++ b/packages/pagebuilder/package.json @@ -44,7 +44,7 @@ "react": "~17.0.1", "react-dom": "~17.0.1", "react-slick": "~0.28.0", - "react-tabs": "~3.0.0", + "react-tabs": "~3.1.0", "react-test-renderer": "~17.0.1" }, "peerDependencies": { From c12e79f2d016704bc682496169e8627834a55c7a Mon Sep 17 00:00:00 2001 From: Ilesh Tiwari Date: Tue, 2 Sep 2025 18:08:57 +0530 Subject: [PATCH 05/36] =?UTF-8?q?Prevent=20Cart=20Creation=20for=20Guest?= =?UTF-8?q?=20User=20and=20Add=20Functionality=20To=20Create=20=E2=80=A6?= =?UTF-8?q?=20(#4537)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Prevent Cart Creation for Guest User and Add Functionality To Create Cart only when Guest User Adds Product to Cart * Fixing Prettier and Snapshot test cases * Removing unwanted and commented out code --- packages/peregrine/lib/context/cart.js | 44 +--- .../__tests__/useAddToCartButton.spec.js | 197 ++++++++++-------- .../lib/talons/Gallery/useAddToCartButton.js | 68 +++++- .../__tests__/useProductFullDetail.spec.js | 3 +- .../ProductFullDetail/useProductFullDetail.js | 55 ++++- 5 files changed, 223 insertions(+), 144 deletions(-) diff --git a/packages/peregrine/lib/context/cart.js b/packages/peregrine/lib/context/cart.js index af34d118c2..15d9bff44e 100644 --- a/packages/peregrine/lib/context/cart.js +++ b/packages/peregrine/lib/context/cart.js @@ -1,15 +1,5 @@ -import React, { - createContext, - useContext, - useEffect, - useMemo, - useCallback -} from 'react'; +import React, { createContext, useContext, useMemo, useCallback } from 'react'; import { connect } from 'react-redux'; -import { useMutation } from '@apollo/client'; -import gql from 'graphql-tag'; - -import { useAwaitQuery } from '@magento/peregrine/lib/hooks/useAwaitQuery'; import actions from '../store/actions/cart/actions'; import * as asyncActions from '../store/actions/cart/asyncActions'; import bindActionCreators from '../util/bindActionCreators'; @@ -62,9 +52,6 @@ const CartContextProvider = props => { return [derivedCartState, cartApi]; }, [cartApi, cartState, derivedDetails]); - const [fetchCartId] = useMutation(CREATE_CART_MUTATION); - const fetchCartDetails = useAwaitQuery(CART_DETAILS_QUERY); - // Storage listener to force a state update if cartId changes from another browser tab. const storageListener = useCallback(() => { const storage = new BrowserPersistence(); @@ -77,14 +64,6 @@ const CartContextProvider = props => { useEventListener(globalThis, 'storage', storageListener); - useEffect(() => { - // cartApi.getCartDetails initializes the cart if there isn't one. - cartApi.getCartDetails({ - fetchCartId, - fetchCartDetails - }); - }, [cartApi, fetchCartDetails, fetchCartId]); - return ( {children} @@ -105,24 +84,3 @@ export default connect( )(CartContextProvider); export const useCartContext = () => useContext(CartContext); - -/** - * We normally do not keep GQL queries in Peregrine. All components should pass - * queries to talons/hooks. This is an exception to the rule because it would - * be unecessarily complex to pass these queries to the context provider. - */ -const CREATE_CART_MUTATION = gql` - mutation createCart { - cartId: createEmptyCart - } -`; - -const CART_DETAILS_QUERY = gql` - query checkUserIsAuthed($cartId: String!) { - cart(cart_id: $cartId) { - # The purpose of this query is to check that the user is authorized - # to query on the current cart. Just fetch "id" to keep it small. - id - } - } -`; diff --git a/packages/peregrine/lib/talons/Gallery/__tests__/useAddToCartButton.spec.js b/packages/peregrine/lib/talons/Gallery/__tests__/useAddToCartButton.spec.js index 6cf49a5565..e3187b6632 100644 --- a/packages/peregrine/lib/talons/Gallery/__tests__/useAddToCartButton.spec.js +++ b/packages/peregrine/lib/talons/Gallery/__tests__/useAddToCartButton.spec.js @@ -1,21 +1,33 @@ import React from 'react'; import { useMutation } from '@apollo/client'; import { useHistory } from 'react-router-dom'; +import { useAwaitQuery } from '@magento/peregrine/lib/hooks/useAwaitQuery'; +import { useCartContext } from '../../../context/cart'; import createTestInstance from '../../../util/createTestInstance'; import { useAddToCartButton } from '../useAddToCartButton'; import { useEventingContext } from '../../../context/eventing'; +import { act } from 'react-test-renderer'; jest.mock('@apollo/client', () => ({ - useMutation: jest.fn().mockReturnValue([jest.fn()]) + gql: jest.fn(), + useMutation: jest.fn(), + useApolloClient: jest.fn() +})); + +jest.mock('@magento/peregrine/lib/hooks/useAwaitQuery', () => ({ + useAwaitQuery: jest.fn() })); jest.mock('react-router-dom', () => ({ useHistory: jest.fn().mockReturnValue({ push: jest.fn() }) })); +// Make sure useCartContext returns [state, api] so cartApi.getCartDetails exists jest.mock('../../../context/cart', () => ({ - useCartContext: jest.fn().mockReturnValue([{ cartId: '1234' }]) + useCartContext: jest + .fn() + .mockReturnValue([{ cartId: '1234' }, { getCartDetails: jest.fn() }]) })); jest.mock('../addToCart.gql', () => ({ ADD_ITEM: 'Add Item GQL Mutation' })); @@ -40,9 +52,39 @@ afterAll(() => { global.console.error = originalError; }); +beforeEach(() => { + // Provide safe defaults so hook renders without per-test overrides + useMutation.mockClear(); + useMutation.mockReturnValue([jest.fn()]); // default single-function tuple + + // Ensure useAwaitQuery is a mock function + if (useAwaitQuery && useAwaitQuery.mockClear) { + useAwaitQuery.mockClear(); + useAwaitQuery.mockReturnValue(jest.fn()); + } + + // Ensure useCartContext returns both state and api (if tests want to override later) + if (useCartContext && useCartContext.mockClear) { + useCartContext.mockClear(); + useCartContext.mockReturnValue([ + { cartId: '1234' }, + { getCartDetails: jest.fn() } + ]); + } + + // Reset eventing mock + if (useEventingContext && useEventingContext.mockClear) { + useEventingContext.mockClear(); + useEventingContext.mockReturnValue([{}, { dispatch: jest.fn() }]); + } + + // Reset console spies + warn.mockClear(); + error.mockClear(); +}); + const Component = props => { const talonProps = useAddToCartButton(props); - return ; }; @@ -53,7 +95,6 @@ const getTalonProps = props => { const update = newProps => { tree.update(); - return root.findByType('i').props.talonProps; }; @@ -88,91 +129,66 @@ const defaultProps = { test('returns proper shape', () => { const { talonProps } = getTalonProps(defaultProps); - expect(talonProps).toMatchSnapshot(); }); test('returns isDisabled true if product type is virtual', () => { const { talonProps } = getTalonProps({ - item: { - ...defaultProps.item, - __typename: 'VirtualProduct' - } + item: { ...defaultProps.item, __typename: 'VirtualProduct' } }); - expect(talonProps.isDisabled).toBeTruthy(); }); test('returns isDisabled true if product type is downloadable', () => { const { talonProps } = getTalonProps({ - item: { - ...defaultProps.item, - __typename: 'DownloadableProduct' - } + item: { ...defaultProps.item, __typename: 'DownloadableProduct' } }); - expect(talonProps.isDisabled).toBeTruthy(); }); test('returns isDisabled true if product type is grouped', () => { const { talonProps } = getTalonProps({ - item: { - ...defaultProps.item, - __typename: 'GroupedProduct' - } + item: { ...defaultProps.item, __typename: 'GroupedProduct' } }); - expect(talonProps.isDisabled).toBeTruthy(); }); test('returns isDisabled true if product type is bundle', () => { const { talonProps } = getTalonProps({ - item: { - ...defaultProps.item, - __typename: 'BundleProduct' - } + item: { ...defaultProps.item, __typename: 'BundleProduct' } }); - expect(talonProps.isDisabled).toBeTruthy(); }); test('returns isDisabled false if product type is simple', () => { const { talonProps } = getTalonProps({ - item: { - ...defaultProps.item, - __typename: 'SimpleProduct' - } + item: { ...defaultProps.item, __typename: 'SimpleProduct' } }); - expect(talonProps.isDisabled).toBeFalsy(); }); test('returns isInStock true if stock_status is IN_STOCK', () => { const { talonProps } = getTalonProps({ - item: { - ...defaultProps.item, - stock_status: 'IN_STOCK' - } + item: { ...defaultProps.item, stock_status: 'IN_STOCK' } }); - expect(talonProps.isInStock).toBeTruthy(); }); test('returns isInStock false if stock_status is not IN_STOCK', () => { const { talonProps } = getTalonProps({ - item: { - ...defaultProps.item, - stock_status: 'OUT_STOCK' - } + item: { ...defaultProps.item, stock_status: 'OUT_STOCK' } }); - expect(talonProps.isInStock).toBeFalsy(); }); describe('testing handleAddToCart', () => { test('should add to cart if item is a simple product', async () => { - const addToCartMutation = jest.fn(); - useMutation.mockReturnValueOnce([addToCartMutation]); + const fetchCartIdMock = jest.fn(); // for CREATE_CART_MUTATION (first useMutation) + const addToCartMutation = jest.fn(); // for ADD_ITEM (second useMutation) + + useMutation + .mockReturnValueOnce([fetchCartIdMock]) + .mockReturnValueOnce([addToCartMutation]); const { talonProps } = getTalonProps({ item: { @@ -182,7 +198,9 @@ describe('testing handleAddToCart', () => { } }); - await talonProps.handleAddToCart(); + await act(async () => { + await talonProps.handleAddToCart(); + }); expect(addToCartMutation).toHaveBeenCalled(); expect(addToCartMutation.mock.calls[0]).toMatchInlineSnapshot(` @@ -209,10 +227,7 @@ describe('testing handleAddToCart', () => { test('should navigate to PDP page if item is a configurable product', async () => { const push = jest.fn(); - const history = { - push - }; - useHistory.mockReturnValueOnce(history); + useHistory.mockReturnValueOnce({ push }); const { talonProps } = getTalonProps({ item: { @@ -224,7 +239,9 @@ describe('testing handleAddToCart', () => { urlSuffix: '.suffix' }); - await talonProps.handleAddToCart(); + await act(async () => { + await talonProps.handleAddToCart(); + }); expect(push).toHaveBeenCalledWith('/configurable_product.suffix'); }); @@ -238,14 +255,13 @@ describe('testing handleAddToCart', () => { } }); - await talonProps.handleAddToCart(); + await act(async () => { + await talonProps.handleAddToCart(); + }); - expect(warn).toHaveBeenCalled(); - expect(warn.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "Unsupported product type unable to handle.", - ] - `); + expect(warn).toHaveBeenCalledWith( + 'Unsupported product type unable to handle.' + ); }); test('should console warn if item is a grouped product', async () => { @@ -257,14 +273,13 @@ describe('testing handleAddToCart', () => { } }); - await talonProps.handleAddToCart(); + await act(async () => { + await talonProps.handleAddToCart(); + }); - expect(warn).toHaveBeenCalled(); - expect(warn.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "Unsupported product type unable to handle.", - ] - `); + expect(warn).toHaveBeenCalledWith( + 'Unsupported product type unable to handle.' + ); }); test('should console warn if item is a virtual product', async () => { @@ -276,14 +291,13 @@ describe('testing handleAddToCart', () => { } }); - await talonProps.handleAddToCart(); + await act(async () => { + await talonProps.handleAddToCart(); + }); - expect(warn).toHaveBeenCalled(); - expect(warn.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "Unsupported product type unable to handle.", - ] - `); + expect(warn).toHaveBeenCalledWith( + 'Unsupported product type unable to handle.' + ); }); test('should console warn if item is a downloadable product', async () => { @@ -295,20 +309,23 @@ describe('testing handleAddToCart', () => { } }); - await talonProps.handleAddToCart(); + await act(async () => { + await talonProps.handleAddToCart(); + }); - expect(warn).toHaveBeenCalled(); - expect(warn.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "Unsupported product type unable to handle.", - ] - `); + expect(warn).toHaveBeenCalledWith( + 'Unsupported product type unable to handle.' + ); }); test('should console error if the mutation fails', async () => { + const fetchCartIdMock = jest.fn(); const errorMessage = 'Something went wrong'; const addToCartMutation = jest.fn().mockRejectedValueOnce(errorMessage); - useMutation.mockReturnValueOnce([addToCartMutation]); + + useMutation + .mockReturnValueOnce([fetchCartIdMock]) + .mockReturnValueOnce([addToCartMutation]); const { talonProps } = getTalonProps({ item: { @@ -318,20 +335,23 @@ describe('testing handleAddToCart', () => { } }); - await talonProps.handleAddToCart(); + await act(async () => { + await talonProps.handleAddToCart(); + }); - expect(error).toHaveBeenCalledWith(errorMessage); + expect(error).toHaveBeenLastCalledWith(errorMessage); }); test('should dispatch event', async () => { const mockDispatch = jest.fn(); + useEventingContext.mockReturnValue([{}, { dispatch: mockDispatch }]); - useEventingContext.mockReturnValue([ - {}, - { - dispatch: mockDispatch - } - ]); + const fetchCartIdMock = jest.fn(); + const addToCartMutation = jest.fn(); + + useMutation + .mockReturnValueOnce([fetchCartIdMock]) + .mockReturnValueOnce([addToCartMutation]); const { talonProps } = getTalonProps({ item: { @@ -341,10 +361,11 @@ describe('testing handleAddToCart', () => { } }); - await talonProps.handleAddToCart(); - - expect(mockDispatch).toBeCalledTimes(1); + await act(async () => { + await talonProps.handleAddToCart(); + }); + expect(mockDispatch).toHaveBeenCalledTimes(1); expect(mockDispatch.mock.calls[0][0]).toMatchSnapshot(); }); }); diff --git a/packages/peregrine/lib/talons/Gallery/useAddToCartButton.js b/packages/peregrine/lib/talons/Gallery/useAddToCartButton.js index c72e345dc8..e8d523e26a 100644 --- a/packages/peregrine/lib/talons/Gallery/useAddToCartButton.js +++ b/packages/peregrine/lib/talons/Gallery/useAddToCartButton.js @@ -1,11 +1,13 @@ import { useCallback, useState } from 'react'; -import { useMutation } from '@apollo/client'; +import { useMutation, gql } from '@apollo/client'; import { useHistory } from 'react-router-dom'; import { useCartContext } from '../../context/cart'; import { useEventingContext } from '../../context/eventing'; import resourceUrl from '../../util/makeUrl'; import operations from './addToCart.gql'; +import { useAwaitQuery } from '@magento/peregrine/lib/hooks/useAwaitQuery'; +import BrowserPersistence from '../../util/simplePersistence'; /** * @param {String} props.item.uid - uid of item @@ -29,6 +31,20 @@ const UNSUPPORTED_PRODUCT_TYPES = [ 'DownloadableProduct' ]; +const CREATE_CART_MUTATION = gql` + mutation createCart { + cartId: createEmptyCart + } +`; + +const CART_DETAILS_QUERY = gql` + query checkUserIsAuthed($cartId: String!) { + cart(cart_id: $cartId) { + id + } + } +`; + export const useAddToCartButton = props => { const { item, urlSuffix } = props; @@ -36,6 +52,12 @@ export const useAddToCartButton = props => { const [isLoading, setIsLoading] = useState(false); + const [cartState, cartApi] = useCartContext(); + const { cartId } = cartState; + + const [fetchCartId] = useMutation(CREATE_CART_MUTATION); + const fetchCartDetails = useAwaitQuery(CART_DETAILS_QUERY); + const isInStock = item.stock_status === 'IN_STOCK'; const productType = item @@ -52,21 +74,42 @@ export const useAddToCartButton = props => { const history = useHistory(); - const [{ cartId }] = useCartContext(); - const [addToCart] = useMutation(operations.ADD_ITEM); + // helper: ensure we have a valid cartId before adding + const ensureCartId = useCallback(async () => { + let newCartId = cartId; + if (!newCartId) { + console.log('No cart ID found, creating a new cart...'); + await cartApi.getCartDetails({ + fetchCartId, + fetchCartDetails + }); + + newCartId = new BrowserPersistence().getItem('cartId'); + + if (!newCartId) { + throw new Error('Failed to create a new cart'); + } + } + return newCartId; + }, [cartId, cartApi, fetchCartId, fetchCartDetails]); + const handleAddToCart = useCallback(async () => { try { if (productType === 'SimpleProduct' || productType === 'simple') { setIsLoading(true); const quantity = 1; + let newCartId; if (item.uid) { + // ensure cart right before addToCart + newCartId = await ensureCartId(); + await addToCart({ variables: { - cartId, + cartId: newCartId, cartItem: { quantity, entered_options: [ @@ -80,9 +123,12 @@ export const useAddToCartButton = props => { } }); } else { + // ensure cart right before addToCart + newCartId = await ensureCartId(); + await addToCart({ variables: { - cartId, + cartId: newCartId, cartItem: { quantity, sku: item.sku @@ -94,7 +140,7 @@ export const useAddToCartButton = props => { dispatch({ type: 'CART_ADD_ITEM', payload: { - cartId, + cartId: newCartId, sku: item.sku, name: item.name, pricing: { @@ -130,7 +176,15 @@ export const useAddToCartButton = props => { } catch (error) { console.error(error); } - }, [productType, addToCart, cartId, item, dispatch, history, urlSuffix]); + }, [ + productType, + addToCart, + item, + dispatch, + history, + urlSuffix, + ensureCartId + ]); return { handleAddToCart, diff --git a/packages/peregrine/lib/talons/ProductFullDetail/__tests__/useProductFullDetail.spec.js b/packages/peregrine/lib/talons/ProductFullDetail/__tests__/useProductFullDetail.spec.js index f5ebd266c1..03759e9117 100644 --- a/packages/peregrine/lib/talons/ProductFullDetail/__tests__/useProductFullDetail.spec.js +++ b/packages/peregrine/lib/talons/ProductFullDetail/__tests__/useProductFullDetail.spec.js @@ -25,7 +25,8 @@ jest.mock('@apollo/client', () => ({ }, loading: false, error: false - })) + })), + useApolloClient: jest.fn() })); jest.mock('@magento/peregrine/lib/context/user', () => { diff --git a/packages/peregrine/lib/talons/ProductFullDetail/useProductFullDetail.js b/packages/peregrine/lib/talons/ProductFullDetail/useProductFullDetail.js index 90b185d4a5..4f760f6451 100644 --- a/packages/peregrine/lib/talons/ProductFullDetail/useProductFullDetail.js +++ b/packages/peregrine/lib/talons/ProductFullDetail/useProductFullDetail.js @@ -1,6 +1,6 @@ import { useCallback, useState, useMemo } from 'react'; import { useIntl } from 'react-intl'; -import { useMutation, useQuery } from '@apollo/client'; +import { useMutation, useQuery, gql } from '@apollo/client'; import { useCartContext } from '@magento/peregrine/lib/context/cart'; import { useUserContext } from '@magento/peregrine/lib/context/user'; @@ -13,6 +13,8 @@ import mergeOperations from '../../util/shallowMerge'; import defaultOperations from './productFullDetail.gql'; import { useEventingContext } from '../../context/eventing'; import { getOutOfStockVariants } from '@magento/peregrine/lib/util/getOutOfStockVariants'; +import { useAwaitQuery } from '@magento/peregrine/lib/hooks/useAwaitQuery'; +import BrowserPersistence from '../../util/simplePersistence'; const INITIAL_OPTION_CODES = new Map(); const INITIAL_OPTION_SELECTIONS = new Map(); @@ -257,7 +259,9 @@ export const useProductFullDetail = props => { const isSupportedProductType = isSupported(productType); - const [{ cartId }] = useCartContext(); + //const [{ cartId }] = useCartContext(); + const [cartState, cartApi] = useCartContext(); + const { cartId } = cartState; const [{ isSignedIn }] = useUserContext(); const { formatMessage } = useIntl(); @@ -411,6 +415,42 @@ export const useProductFullDetail = props => { return selectedOptions; }, [attributeIdToValuesMap, optionSelections]); + // Cart creation wiring (same approach as useAddToCartButton.js) + const CREATE_CART_MUTATION = gql` + mutation createCart { + cartId: createEmptyCart + } + `; + + const CART_DETAILS_QUERY = gql` + query checkUserIsAuthed($cartId: String!) { + cart(cart_id: $cartId) { + id + } + } + `; + + const [fetchCartId] = useMutation(CREATE_CART_MUTATION); + const fetchCartDetails = useAwaitQuery(CART_DETAILS_QUERY); + + const ensureCartId = useCallback(async () => { + let newCartId = cartId; + if (!newCartId) { + await cartApi.getCartDetails({ + fetchCartId, + fetchCartDetails + }); + + newCartId = new BrowserPersistence().getItem('cartId'); + if (!newCartId) { + throw new Error('Failed to create a new cart'); + } + } + return newCartId; + }, [cartId, cartApi, fetchCartId, fetchCartDetails]); + + // Cart Creation ends + const handleAddToCart = useCallback( async formValues => { const { quantity } = formValues; @@ -435,7 +475,7 @@ export const useProductFullDetail = props => { if (isSupportedProductType) { const variables = { - cartId, + cartId, // will be replaced by ensured cart id below parentSku: payload.parentSku, product: payload.item, quantity: payload.quantity, @@ -484,6 +524,10 @@ export const useProductFullDetail = props => { } try { + //Ensure cart exists *right before* mutation runs + const ensuredCartId = await ensureCartId(); + variables.cartId = ensuredCartId; + await addProductToCart({ variables }); const selectedOptionsLabels = @@ -498,7 +542,7 @@ export const useProductFullDetail = props => { dispatch({ type: 'CART_ADD_ITEM', payload: { - cartId, + cartId: ensuredCartId, sku: product.sku, name: product.name, pricing: product.price, @@ -527,7 +571,8 @@ export const useProductFullDetail = props => { product, productPrice, productType, - selectedOptionsArray + selectedOptionsArray, + ensureCartId ] ); From 72e78b09e2aab9a6a2718ae200192c0ed9fd2c53 Mon Sep 17 00:00:00 2001 From: Aanchal Pawar <97873570+glo82145@users.noreply.github.com> Date: Thu, 4 Sep 2025 16:19:58 +0530 Subject: [PATCH 06/36] PWA-3576::Video component issue in dynamic CMS block (#4541) * PWA-3576::Video component issue in dynamic CMS block * PWA-3576::Video component issue in dynamic CMS block --- .../lib/ContentTypes/Block/configAggregator.js | 16 +++++++++++++++- .../__tests__/__snapshots__/video.spec.js.snap | 6 ++++++ .../pagebuilder/lib/ContentTypes/Video/video.js | 7 +++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/pagebuilder/lib/ContentTypes/Block/configAggregator.js b/packages/pagebuilder/lib/ContentTypes/Block/configAggregator.js index 9830d5bd62..4c569bd0b4 100644 --- a/packages/pagebuilder/lib/ContentTypes/Block/configAggregator.js +++ b/packages/pagebuilder/lib/ContentTypes/Block/configAggregator.js @@ -6,7 +6,21 @@ export default node => { const rawHTML = node.childNodes[0] ? node.childNodes[0].innerHTML : ''; // Sanitize the raw HTML using DOMPurify - const sanitizedHTML = DOMPurify.sanitize(rawHTML); + const sanitizedHTML = DOMPurify.sanitize(rawHTML, { + ADD_TAGS: ['iframe'], + ADD_ATTR: [ + 'allow', + 'allowfullscreen', + 'frameborder', + 'scrolling', + 'src', + 'title', + 'width', + 'height', + 'sandbox' + ], + ALLOWED_URI_REGEXP: /^https?:\/\//i // allow only http/https sources + }); return { // Return the sanitized HTML content, along with the result from getAdvanced diff --git a/packages/pagebuilder/lib/ContentTypes/Video/__tests__/__snapshots__/video.spec.js.snap b/packages/pagebuilder/lib/ContentTypes/Video/__tests__/__snapshots__/video.spec.js.snap index 5aaf36704e..5528e2372c 100644 --- a/packages/pagebuilder/lib/ContentTypes/Video/__tests__/__snapshots__/video.spec.js.snap +++ b/packages/pagebuilder/lib/ContentTypes/Video/__tests__/__snapshots__/video.spec.js.snap @@ -36,9 +36,12 @@ exports[`renders a Video component 1`] = ` >