diff --git a/.eslintrc.js b/.eslintrc.js index dc20ccdcdf7..9e126556193 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -162,6 +162,7 @@ module.exports = { '@wordpress/url', '@woocommerce/blocks-test-utils', '@woocommerce/e2e-utils', + '@woocommerce/e2e-mocks', 'babel-jest', 'dotenv', 'jest-environment-puppeteer', diff --git a/.github/patch-initial-checklist.md b/.github/patch-initial-checklist.md index c1ef0ce8557..3e1453fbe6e 100644 --- a/.github/patch-initial-checklist.md +++ b/.github/patch-initial-checklist.md @@ -21,7 +21,7 @@ The release pull request has been created! This checklist is a guide to follow f - [ ] Run `npm ci` - [ ] Run `npm run package-plugin:deploy`. This will create a zip of the current branch build locally. - [ ] Create the testing notes for the release. - - [ ] For each pull request that belongs to the current release, grab the `User Facing Testing` notes from the PR's description. + - [ ] For each pull request that belongs to the current release, grab the `User Facing Testing` notes from the PR's description. - If a PR has the `Should be tested by the development team exclusively` checkbox checked, create a new section called 'Testing notes for the development team' and copy the `User Facing Testing` notes from the PR to this section. - If a PR has the `Experimental` checkbox checked, do not include it in the testing instructions. - If a PR has the `Do not include in the Testing Notes` checkbox checked, as the description suggests, do not include it in the release instructions. @@ -66,7 +66,7 @@ Each porter is responsible for testing the PRs that fall under the focus of thei ## After Deploy -- [ ] Port to `trunk` the changes to the changelog, testing steps and required versions that you did in the previous steps. You can do so copy-and-pasting the changes in a new commit directly to `trunk`, or cherry-picking the commits that introduced those changes. +- [ ] Move the changes to the changelog, testing steps and required versions that you did in the previous steps to `trunk`. You can do so copy-and-pasting the changes in a new commit directly to `trunk`, or cherry-picking the commits that introduced those changes. - [ ] Update the schedules p2 with the shipped date for the release (PdToLP-K-p2). - [ ] Edit the GitHub milestone of the release you just shipped and add the current date as the due date (this is used to track ship date as well). diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 77dcfbddef5..77f23181e58 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,47 +1,19 @@ - + - +## What Fixes # - - +## Why -#### Accessibility + - - -- [ ] I've tested using only a keyboard (no mouse) -- [ ] I've tested using a screen reader -- [ ] All animations respect [`prefers-reduced-motion`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion) -- [ ] All text has [at least a 4.5 color contrast with its background](https://webaim.org/resources/contrastchecker/) - -#### Other Checks - -- [ ] This PR has either a `[type]` label or a `[skip-changelog]` label. -- [ ] This PR adds/removes a feature flag & I've updated [this doc](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/internal-developers/blocks/feature-flags-and-experimental-interfaces.md). -- [ ] This PR adds/removes an experimental interfaces and I've updated [this doc](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/internal-developers/blocks/feature-flags-and-experimental-interfaces.md). -- [ ] I tagged two reviewers because this PR makes queries to the database or I think it might have some security impact. - -### Screenshots - - - -| Before | After | -| ------ | ----- | -| | | - -### Testing - -#### Automated Tests -* [ ] Changes in this PR are covered by Automated Tests. - * [ ] Unit tests - * [ ] E2E tests - -#### User Facing Testing +## Testing Instructions +_Please consider any edge cases this change may have, and also other areas of the product this may impact._ + 1. 2. 3. @@ -49,18 +21,38 @@ Fixes # * [ ] Do not include in the Testing Notes * [ ] Should be tested by the development team exclusively -### WooCommerce Visibility +## Screenshots or screencast + + + +| Before | After | +| ------ | ----- | +| | | - +## WooCommerce Visibility + + +Required: * [ ] WooCommerce Core * [ ] Feature plugin * [ ] Experimental +* [ ] N/A + +## Checklist -### Performance Impact +Required: +* [ ] This PR has either a `[type]` label or a `[skip-changelog]` label. +* [ ] This PR is assigned to a milestone. - +Conditional: +* [ ] This PR has a changelog description (if `[skip-changelog]` label is not present). +* [ ] This PR adds/removes a feature flag & I've updated [this doc](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/internal-developers/blocks/feature-flags-and-experimental-interfaces.md). +* [ ] This PR adds/removes an experimental interfaces, and I've updated [this doc](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/internal-developers/blocks/feature-flags-and-experimental-interfaces.md). +* [ ] This PR has been accessibility tested. +* [ ] This PR has had any necessary documentation added/updated. -### Changelog +## Changelog + > Add suggested changelog entry here. diff --git a/.github/release-initial-checklist.md b/.github/release-initial-checklist.md index 1c64ff15f77..958a8bc038e 100644 --- a/.github/release-initial-checklist.md +++ b/.github/release-initial-checklist.md @@ -81,7 +81,7 @@ Each porter is responsible for testing the PRs that fall under the focus of thei ## After Workflow completes -- [ ] Port to `trunk` the changes to the changelog, testing steps and required versions that you did in the previous steps. You can do so copy-and-pasting the changes in a new commit directly to `trunk`, or cherry-picking the commits that introduced those changes. +- [ ] Move the changes to the changelog, testing steps and required versions that you did in the previous steps to `trunk`. You can do so copy-and-pasting the changes in a new commit directly to `trunk`, or cherry-picking the commits that introduced those changes. - [ ] Run `npm run change-versions` to update the version in `trunk` to the next version of the plugin and include the `dev` suffix. For example, if you released 2.5.0, you should update the version in `trunk` to 2.6.0-dev. - [ ] Update the schedules p2 with the shipped date for the release (PdToLP-K-p2). - [ ] Edit the GitHub milestone of the release you just shipped and add the current date as the due date (this is used to track ship date as well). diff --git a/.github/workflows/php-js-e2e-tests.yml b/.github/workflows/php-js-e2e-tests.yml index 157d3e00244..3000500b053 100644 --- a/.github/workflows/php-js-e2e-tests.yml +++ b/.github/workflows/php-js-e2e-tests.yml @@ -153,6 +153,7 @@ jobs: env: WOOCOMMERCE_BLOCKS_PHASE: 3 run: | + node ./bin/wp-env-with-wp-622.js npm run wp-env start npm run wp-env:config && npx cross-env NODE_CONFIG_DIR=tests/e2e-jest/config wp-scripts test-e2e --config tests/e2e-jest/config/jest.config.js --listTests > ~/.jest-e2e-tests npx cross-env JEST_PUPPETEER_CONFIG=tests/e2e-jest/config/jest-puppeteer.config.js cross-env NODE_CONFIG_DIR=tests/e2e-jest/config wp-scripts test-e2e --config tests/e2e-jest/config/jest.config.js --runInBand --runTestsByPath $( awk 'NR % 5 == ${{ matrix.part }} - 1' < ~/.jest-e2e-tests ) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 5410fe1e3ae..db56521ef34 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -7,13 +7,32 @@ on: jobs: PlaywrightE2ETests: - name: Playwright E2E tests + name: Playwright E2E tests - ${{ matrix.config.name }} timeout-minutes: 60 runs-on: ubuntu-latest + strategy: + matrix: + config: [ + { name: Normal, file: playwright.config.ts, resultPath: test-results }, + { name: SideEffects, file: playwright.side-effects.config.ts, resultPath: test-results-side-effects }, + ] steps: - uses: actions/checkout@v3 + - name: Cache node_modules + id: cache-node-modules + uses: actions/cache@v3 + env: + cache-name: cache-node-modules + with: + path: node_modules + key: ${{ runner.os }}-modified-build-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-modified-build-${{ env.cache-name }}- + ${{ runner.os }}-modified-build- + ${{ runner.os }}-modified- + - name: Setup node version and npm cache uses: actions/setup-node@v3 with: @@ -21,6 +40,7 @@ jobs: cache: 'npm' - name: Install Node dependencies + if: steps.cache-node-modules.outputs.cache-hit != 'true' run: npm ci - name: Build Assets @@ -37,7 +57,8 @@ jobs: with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- + restore-keys: | + ${{ runner.os }}-composer- - name: Set up PHP uses: shivammathur/setup-php@v2 @@ -52,18 +73,17 @@ jobs: - name: Install Playwright run: npx playwright install --with-deps - - name: Load wp-env run: npm run env:start - name: Run Playwright tests - run: npm run test:e2e + run: npm run test:e2e -- --config=tests/e2e/${{ matrix.config.file }} - uses: actions/upload-artifact@v3 if: ${{ failure() }} with: - name: playwright-report - path: artifacts/test-results + name: playwright-report-${{ matrix.config.name }} + path: artifacts/${{ matrix.config.resultPath }} if-no-files-found: error # 'warn' or 'ignore' are also available, defaults to `warn` diff --git a/.wordpress-org/banner-1544x500.png b/.wordpress-org/banner-1544x500.png index 0571083ad86..3a83246f164 100644 Binary files a/.wordpress-org/banner-1544x500.png and b/.wordpress-org/banner-1544x500.png differ diff --git a/.wordpress-org/banner-772x250.png b/.wordpress-org/banner-772x250.png index 0ca87162671..b932f21dd77 100644 Binary files a/.wordpress-org/banner-772x250.png and b/.wordpress-org/banner-772x250.png differ diff --git a/.wordpress-org/icon-128x128.png b/.wordpress-org/icon-128x128.png index 26530882c0c..6d5a0f50715 100644 Binary files a/.wordpress-org/icon-128x128.png and b/.wordpress-org/icon-128x128.png differ diff --git a/.wordpress-org/icon-256x256.png b/.wordpress-org/icon-256x256.png index 127951966e6..1ff4a1d6106 100644 Binary files a/.wordpress-org/icon-256x256.png and b/.wordpress-org/icon-256x256.png differ diff --git a/.wp-env.json b/.wp-env.json index 14c1c790faa..d290c174e0d 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -1,5 +1,5 @@ { - "core": "WordPress/WordPress#6.2.2", + "core": null, "plugins": [ "https://downloads.wordpress.org/plugin/woocommerce.latest-stable.zip", "https://github.com/WP-API/Basic-Auth/archive/master.zip", @@ -14,7 +14,8 @@ "wp-content/plugins/gutenberg-test-plugins": "./node_modules/@wordpress/e2e-tests/plugins", "wp-content/plugins/woocommerce-blocks": ".", "wp-content/plugins/woocommerce-gutenberg-products-block": ".", - "wp-cli.yml": "./wp-cli.yml" + "wp-cli.yml": "./wp-cli.yml", + "custom-plugins" : "./tests/e2e/mocks/custom-plugins" } } }, diff --git a/assets/css/editor.scss b/assets/css/editor.scss index 8ed065aded1..b721f90811c 100644 --- a/assets/css/editor.scss +++ b/assets/css/editor.scss @@ -8,6 +8,10 @@ .wc-block-grid__product { margin: 0 0 $gap-large 0; + + .wc-block-grid__product-onsale { + position: absolute; + } } } diff --git a/assets/css/style.scss b/assets/css/style.scss index c1f78af42f6..f024fe53caf 100644 --- a/assets/css/style.scss +++ b/assets/css/style.scss @@ -139,7 +139,8 @@ } } } -.wc-block-grid__product-onsale { +.wc-block-grid__product-image .wc-block-grid__product-onsale, +.wc-block-grid .wc-block-grid__product-onsale { @include font-size(small); padding: em($gap-smallest) em($gap-small); display: inline-block; diff --git a/assets/js/atomic/blocks/product-elements/button/block.json b/assets/js/atomic/blocks/product-elements/button/block.json index 62f1889af91..c69913bdee2 100644 --- a/assets/js/atomic/blocks/product-elements/button/block.json +++ b/assets/js/atomic/blocks/product-elements/button/block.json @@ -34,6 +34,7 @@ "background": false, "link": true }, + "interactivity": true, "html": false, "typography": { "fontSize": true, @@ -57,6 +58,9 @@ "label": "Outline" } ], + "viewScript": [ + "wc-product-button-interactivity-frontend" + ], "apiVersion": 2, "$schema": "https://schemas.wp.org/trunk/block.json" } diff --git a/assets/js/atomic/blocks/product-elements/button/frontend.tsx b/assets/js/atomic/blocks/product-elements/button/frontend.tsx new file mode 100644 index 00000000000..9f1c8bc995d --- /dev/null +++ b/assets/js/atomic/blocks/product-elements/button/frontend.tsx @@ -0,0 +1,279 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/** + * External dependencies + */ +import { CART_STORE_KEY as storeKey } from '@woocommerce/block-data'; +import { store as interactivityStore } from '@woocommerce/interactivity'; +import { dispatch, select, subscribe } from '@wordpress/data'; +import { Cart } from '@woocommerce/type-defs/cart'; +import { createRoot } from '@wordpress/element'; +import NoticeBanner from '@woocommerce/base-components/notice-banner'; + +type Context = { + woocommerce: { + isLoading: boolean; + addToCartText: string; + productId: number; + displayViewCart: boolean; + quantityToAdd: number; + temporaryNumberOfItems: number; + animationStatus: AnimationStatus; + }; +}; + +enum AnimationStatus { + IDLE = 'IDLE', + SLIDE_OUT = 'SLIDE-OUT', + SLIDE_IN = 'SLIDE-IN', +} + +type State = { + woocommerce: { + cart: Cart | undefined; + inTheCartText: string; + }; +}; + +type Store = { + state: State; + context: Context; + selectors: any; + ref: HTMLElement; +}; + +const storeNoticeClass = '.wc-block-store-notices'; + +const createNoticeContainer = () => { + const noticeContainer = document.createElement( 'div' ); + noticeContainer.classList.add( storeNoticeClass.replace( '.', '' ) ); + return noticeContainer; +}; + +const injectNotice = ( domNode: Element, errorMessage: string ) => { + const root = createRoot( domNode ); + + root.render( + root.unmount() }> + { errorMessage } + + ); + + domNode?.scrollIntoView( { + behavior: 'smooth', + inline: 'nearest', + } ); +}; + +const getProductById = ( cartState: Cart | undefined, productId: number ) => { + return cartState?.items.find( ( item ) => item.id === productId ); +}; + +const getTextButton = ( { + addToCartText, + inTheCartText, + numberOfItems, +}: { + addToCartText: string; + inTheCartText: string; + numberOfItems: number; +} ) => { + if ( numberOfItems === 0 ) { + return addToCartText; + } + return inTheCartText.replace( '###', numberOfItems.toString() ); +}; + +const productButtonSelectors = { + woocommerce: { + addToCartText: ( store: Store ) => { + const { context, state, selectors } = store; + + // We use the temporary number of items when there's no animation, or the + // second part of the animation hasn't started. + if ( + context.woocommerce.animationStatus === AnimationStatus.IDLE || + context.woocommerce.animationStatus === + AnimationStatus.SLIDE_OUT + ) { + return getTextButton( { + addToCartText: context.woocommerce.addToCartText, + inTheCartText: state.woocommerce.inTheCartText, + numberOfItems: context.woocommerce.temporaryNumberOfItems, + } ); + } + + return getTextButton( { + addToCartText: context.woocommerce.addToCartText, + inTheCartText: state.woocommerce.inTheCartText, + numberOfItems: + selectors.woocommerce.numberOfItemsInTheCart( store ), + } ); + }, + displayViewCart: ( store: Store ) => { + const { context, selectors } = store; + if ( ! context.woocommerce.displayViewCart ) return false; + if ( ! selectors.woocommerce.hasCartLoaded( store ) ) { + return context.woocommerce.temporaryNumberOfItems > 0; + } + return selectors.woocommerce.numberOfItemsInTheCart( store ) > 0; + }, + hasCartLoaded: ( { state }: { state: State } ) => { + return state.woocommerce.cart !== undefined; + }, + numberOfItemsInTheCart: ( { state, context }: Store ) => { + const product = getProductById( + state.woocommerce.cart, + context.woocommerce.productId + ); + return product?.quantity || 0; + }, + slideOutAnimation: ( { context }: Store ) => + context.woocommerce.animationStatus === AnimationStatus.SLIDE_OUT, + slideInAnimation: ( { context }: Store ) => + context.woocommerce.animationStatus === AnimationStatus.SLIDE_IN, + }, +}; + +interactivityStore( + // @ts-expect-error: Store function isn't typed. + { + selectors: productButtonSelectors, + actions: { + woocommerce: { + addToCart: async ( store: Store ) => { + const { context, selectors, ref } = store; + + if ( ! ref.classList.contains( 'ajax_add_to_cart' ) ) { + return; + } + + context.woocommerce.isLoading = true; + + // Allow 3rd parties to validate and quit early. + // https://github.com/woocommerce/woocommerce/blob/154dd236499d8a440edf3cde712511b56baa8e45/plugins/woocommerce/client/legacy/js/frontend/add-to-cart.js/#L74-L77 + const event = new CustomEvent( + 'should_send_ajax_request.adding_to_cart', + { detail: [ ref ], cancelable: true } + ); + const shouldSendRequest = + document.body.dispatchEvent( event ); + + if ( shouldSendRequest === false ) { + const ajaxNotSentEvent = new CustomEvent( + 'ajax_request_not_sent.adding_to_cart', + { detail: [ false, false, ref ] } + ); + document.body.dispatchEvent( ajaxNotSentEvent ); + return true; + } + + try { + await dispatch( storeKey ).addItemToCart( + context.woocommerce.productId, + context.woocommerce.quantityToAdd + ); + + // After the cart has been updated, sync the temporary number of + // items again. + context.woocommerce.temporaryNumberOfItems = + selectors.woocommerce.numberOfItemsInTheCart( + store + ); + } catch ( error ) { + const storeNoticeBlock = + document.querySelector( storeNoticeClass ); + + if ( ! storeNoticeBlock ) { + document + .querySelector( '.entry-content' ) + ?.prepend( createNoticeContainer() ); + } + + const domNode = + storeNoticeBlock ?? + document.querySelector( storeNoticeClass ); + + if ( domNode ) { + injectNotice( domNode, error.message ); + } + + // We don't care about errors blocking execution, but will + // console.error for troubleshooting. + // eslint-disable-next-line no-console + console.error( error ); + } finally { + context.woocommerce.displayViewCart = true; + context.woocommerce.isLoading = false; + } + }, + handleAnimationEnd: ( + store: Store & { event: AnimationEvent } + ) => { + const { event, context, selectors } = store; + if ( event.animationName === 'slideOut' ) { + // When the first part of the animation (slide-out) ends, we move + // to the second part (slide-in). + context.woocommerce.animationStatus = + AnimationStatus.SLIDE_IN; + } else if ( event.animationName === 'slideIn' ) { + // When the second part of the animation ends, we update the + // temporary number of items to sync it with the cart and reset the + // animation status so it can be triggered again. + context.woocommerce.temporaryNumberOfItems = + selectors.woocommerce.numberOfItemsInTheCart( + store + ); + context.woocommerce.animationStatus = + AnimationStatus.IDLE; + } + }, + }, + }, + effects: { + woocommerce: { + startAnimation: ( store: Store ) => { + const { context, selectors } = store; + // We start the animation if the cart has loaded, the temporary number + // of items is out of sync with the number of items in the cart, the + // button is not loading (because that means the user started the + // interaction) and the animation hasn't started yet. + if ( + selectors.woocommerce.hasCartLoaded( store ) && + context.woocommerce.temporaryNumberOfItems !== + selectors.woocommerce.numberOfItemsInTheCart( + store + ) && + ! context.woocommerce.isLoading && + context.woocommerce.animationStatus === + AnimationStatus.IDLE + ) { + context.woocommerce.animationStatus = + AnimationStatus.SLIDE_OUT; + } + }, + }, + }, + }, + { + afterLoad: ( store: Store ) => { + const { state, selectors } = store; + // Subscribe to changes in Cart data. + subscribe( () => { + const cartData = select( storeKey ).getCartData(); + const isResolutionFinished = + select( storeKey ).hasFinishedResolution( 'getCartData' ); + if ( isResolutionFinished ) { + state.woocommerce.cart = cartData; + } + }, storeKey ); + + // This selector triggers a fetch of the Cart data. It is done in a + // `requestIdleCallback` to avoid potential performance issues. + requestIdleCallback( () => { + if ( ! selectors.woocommerce.hasCartLoaded( store ) ) { + select( storeKey ).getCartData(); + } + } ); + }, + } +); diff --git a/assets/js/atomic/blocks/product-elements/button/style.scss b/assets/js/atomic/blocks/product-elements/button/style.scss index e0b95ed385e..6bcdaaf834d 100644 --- a/assets/js/atomic/blocks/product-elements/button/style.scss +++ b/assets/js/atomic/blocks/product-elements/button/style.scss @@ -1,15 +1,78 @@ .wp-block-button.wc-block-components-product-button { word-break: break-word; white-space: normal; + display: flex; + justify-content: center; + align-items: center; + gap: $gap-small; + + .wp-block-button__link { + word-break: break-word; + white-space: normal; + display: inline-flex; + justify-content: center; + text-align: center; + // Set button font size and padding so it inherits from parent. + padding: 0.5em 1em; + font-size: 1em; + + &.loading { + opacity: 0.25; + } + + &.loading::after { + font-family: WooCommerce; /* stylelint-disable-line */ + content: "\e031"; + animation: spin 2s linear infinite; + margin-left: 0.5em; + display: inline-block; + width: auto; + height: auto; + } + } + + a[hidden] { + display: none; + } + + @keyframes slideOut { + from { + transform: translateY(0); + } + to { + transform: translateY(-100%); + } + } + + @keyframes slideIn { + from { + transform: translateY(90%); + opacity: 0; + } + to { + transform: translate(0); + opacity: 1; + } + } .wc-block-components-product-button__button { border-style: none; display: inline-flex; justify-content: center; - margin-right: auto; - margin-left: auto; white-space: normal; word-break: break-word; + width: 150px; + overflow: hidden; + + span { + + &.wc-block-slide-out { + animation: slideOut 0.1s linear 1 normal forwards; + } + &.wc-block-slide-in { + animation: slideIn 0.1s linear 1 normal; + } + } } .wc-block-components-product-button__button--placeholder { diff --git a/assets/js/atomic/blocks/product-elements/product-details/block.json b/assets/js/atomic/blocks/product-elements/product-details/block.json index 2919a826fab..a8e95dd56ac 100644 --- a/assets/js/atomic/blocks/product-elements/product-details/block.json +++ b/assets/js/atomic/blocks/product-elements/product-details/block.json @@ -3,11 +3,14 @@ "version": "1.0.0", "icon": "info", "title": "Product Details", - "description": "Display a product’s description, attributes, and reviews.", + "description": "Display a product's description, attributes, and reviews.", "category": "woocommerce", "keywords": [ "WooCommerce" ], "supports": { - "align": true + "align": true, + "spacing": { + "margin": true + } }, "textdomain": "woo-gutenberg-products-block", "apiVersion": 2, diff --git a/assets/js/blocks/attribute-filter/edit.tsx b/assets/js/blocks/attribute-filter/edit.tsx index 8bcf54e4f75..5cf53e044bf 100644 --- a/assets/js/blocks/attribute-filter/edit.tsx +++ b/assets/js/blocks/attribute-filter/edit.tsx @@ -352,6 +352,7 @@ const Edit = ( { href={ getAdminLink( 'edit.php?post_type=product&page=product_attributes' ) } + target="_top" > { __( 'Add new attribute', 'woo-gutenberg-products-block' ) + ' ' } @@ -361,6 +362,7 @@ const Edit = ( { className="wc-block-attribute-filter__read_more_button" isTertiary href="https://docs.woocommerce.com/document/managing-product-taxonomies/" + target="_blank" > { __( 'Learn more', 'woo-gutenberg-products-block' ) } diff --git a/assets/js/blocks/cart-checkout-shared/payment-methods/express-payment/style.scss b/assets/js/blocks/cart-checkout-shared/payment-methods/express-payment/style.scss index 990809e7fac..9fac0bd0e15 100644 --- a/assets/js/blocks/cart-checkout-shared/payment-methods/express-payment/style.scss +++ b/assets/js/blocks/cart-checkout-shared/payment-methods/express-payment/style.scss @@ -5,18 +5,13 @@ $border-radius: 5px; margin: auto; position: relative; + // nested class to avoid conflict with .editor-styles-wrapper ul .wc-block-components-express-payment__event-buttons { - list-style: none; - display: grid; - grid-template-columns: repeat(auto-fit, minmax(calc(33% - 10px), 1fr)); - grid-gap: 10px; - box-sizing: border-box; width: 100%; padding: 0; margin: 0; overflow: hidden; text-align: center; - > li { margin: 0; width: 100%; @@ -27,18 +22,23 @@ $border-radius: 5px; } } } - - @include breakpoint("<782px") { - .wc-block-components-express-payment__event-buttons { - grid-template-columns: 1fr; - } - } } .wc-block-components-express-payment--checkout { /* stylelint-disable-next-line function-calc-no-unspaced-operator */ margin-top: calc($border-radius * 3); + .wc-block-components-express-payment__event-buttons { + list-style: none; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(calc(33% - 10px), 1fr)); + grid-gap: 10px; + + @include breakpoint("<782px") { + grid-template-columns: 1fr; + } + } + .wc-block-components-express-payment__title-container { display: flex; flex-direction: row; diff --git a/assets/js/blocks/migration-products-to-product-collection/constants.ts b/assets/js/blocks/migration-products-to-product-collection/constants.ts new file mode 100644 index 00000000000..2f68b429ecf --- /dev/null +++ b/assets/js/blocks/migration-products-to-product-collection/constants.ts @@ -0,0 +1,19 @@ +/** + * Internal dependencies + */ +import type { UpgradeNoticeStatus, UpgradeNoticeStatuses } from './types'; + +export const AUTO_REPLACE_PRODUCTS_WITH_PRODUCT_COLLECTION = false; +export const MANUAL_REPLACE_PRODUCTS_WITH_PRODUCT_COLLECTION = false; +export const HOURS_TO_DISPLAY_UPGRADE_NOTICE = 72; +export const UPGRADE_NOTICE_DISPLAY_COUNT_THRESHOLD = 4; +export const MIGRATION_STATUS_LS_KEY = + 'wc-blocks_upgraded-products-to-product-collection'; +// Initial status used in the localStorage +export const INITIAL_STATUS_LS_VALUE: UpgradeNoticeStatuses = 'notseen'; + +export const getInitialStatusLSValue: () => UpgradeNoticeStatus = () => ( { + status: INITIAL_STATUS_LS_VALUE, + time: Date.now(), + displayCount: 0, +} ); diff --git a/assets/js/blocks/shared/scripts/index.tsx b/assets/js/blocks/migration-products-to-product-collection/index.ts similarity index 59% rename from assets/js/blocks/shared/scripts/index.tsx rename to assets/js/blocks/migration-products-to-product-collection/index.ts index b255b01a57d..9067c26005e 100644 --- a/assets/js/blocks/shared/scripts/index.tsx +++ b/assets/js/blocks/migration-products-to-product-collection/index.ts @@ -1,2 +1,5 @@ export * from './migration-from-products-to-product-collection'; export * from './migration-from-product-collection-to-products'; +export * from './migration-utils'; +export * from './constants'; +export * from './types'; diff --git a/assets/js/blocks/shared/scripts/migration-from-product-collection-to-products.tsx b/assets/js/blocks/migration-products-to-product-collection/migration-from-product-collection-to-products.ts similarity index 91% rename from assets/js/blocks/shared/scripts/migration-from-product-collection-to-products.tsx rename to assets/js/blocks/migration-products-to-product-collection/migration-from-product-collection-to-products.ts index 57bca80ad16..18444ae11ee 100644 --- a/assets/js/blocks/shared/scripts/migration-from-product-collection-to-products.tsx +++ b/assets/js/blocks/migration-products-to-product-collection/migration-from-product-collection-to-products.ts @@ -7,17 +7,21 @@ import { select, dispatch } from '@wordpress/data'; /** * Internal dependencies */ +import { disableAutoUpdate } from './migration-from-products-to-product-collection'; import { getProductCollectionBlockClientIds, checkIfBlockCanBeInserted, postTemplateHasSupportForGridView, - type TransformBlock, - type IsBlockType, - type ProductGridLayout, - type ProductGridLayoutTypes, - type PostTemplateLayout, - type PostTemplateLayoutTypes, + setUpgradeStatus, } from './migration-utils'; +import type { + TransformBlock, + IsBlockType, + ProductGridLayout, + ProductGridLayoutTypes, + PostTemplateLayout, + PostTemplateLayoutTypes, +} from './types'; const VARIATION_NAME = 'woocommerce/product-query'; @@ -45,6 +49,10 @@ const mapAttributes = ( attributes ) => { mappedQuery.__woocommerceOnSale = woocommerceOnSale; } + if ( taxQuery ) { + mappedQuery.taxQuery = taxQuery; + } + return { ...restAttributes, namespace: VARIATION_NAME, @@ -207,3 +215,12 @@ export const replaceProductCollectionWithProducts = () => { productCollectionBlockClientIds.map( replaceProductCollectionBlock ); }; + +export const revertMigration = () => { + disableAutoUpdate(); + setUpgradeStatus( { + status: 'reverted', + time: Date.now(), + } ); + replaceProductCollectionWithProducts(); +}; diff --git a/assets/js/blocks/shared/scripts/migration-from-products-to-product-collection.tsx b/assets/js/blocks/migration-products-to-product-collection/migration-from-products-to-product-collection.ts similarity index 82% rename from assets/js/blocks/shared/scripts/migration-from-products-to-product-collection.tsx rename to assets/js/blocks/migration-products-to-product-collection/migration-from-products-to-product-collection.ts index 2a803057442..0de18ef3e08 100644 --- a/assets/js/blocks/shared/scripts/migration-from-products-to-product-collection.tsx +++ b/assets/js/blocks/migration-products-to-product-collection/migration-from-products-to-product-collection.ts @@ -2,22 +2,31 @@ * External dependencies */ import { createBlock, BlockInstance } from '@wordpress/blocks'; -import { select, dispatch } from '@wordpress/data'; +import { select, dispatch, subscribe } from '@wordpress/data'; +import { isWpVersion } from '@woocommerce/settings'; /** * Internal dependencies */ +import { + AUTO_REPLACE_PRODUCTS_WITH_PRODUCT_COLLECTION, + getInitialStatusLSValue, +} from './constants'; import { getProductsBlockClientIds, checkIfBlockCanBeInserted, postTemplateHasSupportForGridView, - type TransformBlock, - type IsBlockType, - type ProductGridLayout, - type ProductGridLayoutTypes, - type PostTemplateLayout, - type PostTemplateLayoutTypes, + getUpgradeStatus, + setUpgradeStatus, } from './migration-utils'; +import type { + TransformBlock, + IsBlockType, + ProductGridLayout, + ProductGridLayoutTypes, + PostTemplateLayout, + PostTemplateLayoutTypes, +} from './types'; const mapAttributes = ( attributes: Record< string, unknown > ) => { const { query, namespace, ...restAttributes } = attributes; @@ -41,7 +50,7 @@ const mapAttributes = ( attributes: Record< string, unknown > ) => { isProductCollectionBlock: true, ...restQuery, }, - displayUpgradeNotice: true, + convertedFromProducts: true, }; }; @@ -194,9 +203,7 @@ const replaceProductsBlocks = ( productsBlockClientIds: string[] ) => { return !! results.length && results.every( ( result ) => !! result ); }; -export const replaceProductsWithProductCollection = ( - unsubscribe?: () => void -) => { +export const replaceProductsWithProductCollection = () => { const queryBlocksCount = select( 'core/block-editor' ).getGlobalBlockCount( 'core/query' ); if ( queryBlocksCount === 0 ) { @@ -211,10 +218,32 @@ export const replaceProductsWithProductCollection = ( return; } - const replaced = replaceProductsBlocks( productsBlockClientIds ); + replaceProductsBlocks( productsBlockClientIds ); +}; + +export const manualUpdate = () => { + setUpgradeStatus( getInitialStatusLSValue() ); + replaceProductsWithProductCollection(); +}; - if ( unsubscribe && replaced ) { - // @todo: unsubscribe on user reverting migration +let unsubscribe: ( () => void ) | undefined; +export const disableAutoUpdate = () => { + if ( unsubscribe ) { unsubscribe(); } }; +export const enableAutoUpdate = () => { + if ( isWpVersion( '6.1', '>=' ) ) { + const { status } = getUpgradeStatus(); + + if ( + AUTO_REPLACE_PRODUCTS_WITH_PRODUCT_COLLECTION && + status !== 'reverted' && + ! unsubscribe + ) { + unsubscribe = subscribe( () => { + replaceProductsWithProductCollection(); + }, 'core/block-editor' ); + } + } +}; diff --git a/assets/js/blocks/shared/scripts/migration-utils.tsx b/assets/js/blocks/migration-products-to-product-collection/migration-utils.ts similarity index 60% rename from assets/js/blocks/shared/scripts/migration-utils.tsx rename to assets/js/blocks/migration-products-to-product-collection/migration-utils.ts index 9748ab82de0..a88a0af297f 100644 --- a/assets/js/blocks/shared/scripts/migration-utils.tsx +++ b/assets/js/blocks/migration-products-to-product-collection/migration-utils.ts @@ -4,33 +4,25 @@ import { getSettingWithCoercion } from '@woocommerce/settings'; import { type BlockInstance } from '@wordpress/blocks'; import { select } from '@wordpress/data'; -import { isBoolean } from '@woocommerce/types'; +import { isBoolean, isNumber } from '@woocommerce/types'; -type GetBlocksClientIds = ( blocks: BlockInstance[] ) => string[]; -export type IsBlockType = ( block: BlockInstance ) => boolean; -export type TransformBlock = ( - block: BlockInstance, - innerBlock: BlockInstance[] -) => BlockInstance; -export type ProductGridLayoutTypes = 'flex' | 'list'; -export type PostTemplateLayoutTypes = 'grid' | 'default'; - -export type ProductGridLayout = { - type: ProductGridLayoutTypes; - columns: number; -}; - -export type PostTemplateLayout = { - type: PostTemplateLayoutTypes; - columnCount: number; -}; +/** + * Internal dependencies + */ +import { MIGRATION_STATUS_LS_KEY, getInitialStatusLSValue } from './constants'; +import type { + IsBlockType, + GetBlocksClientIds, + UpgradeNoticeStatus, +} from './types'; const isProductsBlock: IsBlockType = ( block ) => block.name === 'core/query' && block.attributes.namespace === 'woocommerce/product-query'; -const isProductCollectionBlock: IsBlockType = ( block ) => - block.name === 'woocommerce/product-collection'; +const isConvertedProductCollectionBlock: IsBlockType = ( block ) => + block.name === 'woocommerce/product-collection' && + block.attributes.convertedFromProducts; const getBlockClientIdsByPredicate = ( blocks: BlockInstance[], @@ -53,7 +45,7 @@ const getProductsBlockClientIds: GetBlocksClientIds = ( blocks ) => getBlockClientIdsByPredicate( blocks, isProductsBlock ); const getProductCollectionBlockClientIds: GetBlocksClientIds = ( blocks ) => - getBlockClientIdsByPredicate( blocks, isProductCollectionBlock ); + getBlockClientIdsByPredicate( blocks, isConvertedProductCollectionBlock ); const checkIfBlockCanBeInserted = ( clientId: string, @@ -78,9 +70,35 @@ const postTemplateHasSupportForGridView = getSettingWithCoercion( isBoolean ); +const getUpgradeStatus = (): UpgradeNoticeStatus => { + const status = window.localStorage.getItem( MIGRATION_STATUS_LS_KEY ); + return status ? JSON.parse( status ) : getInitialStatusLSValue(); +}; + +const setUpgradeStatus = ( newStatus: UpgradeNoticeStatus ) => { + window.localStorage.setItem( + MIGRATION_STATUS_LS_KEY, + JSON.stringify( newStatus ) + ); +}; + +const incrementUpgradeStatusDisplayCount = () => { + const status = getUpgradeStatus(); + const displayCount = isNumber( status.displayCount ) + ? status.displayCount + 1 + : 0; + setUpgradeStatus( { + ...status, + displayCount, + } ); +}; + export { getProductsBlockClientIds, getProductCollectionBlockClientIds, checkIfBlockCanBeInserted, postTemplateHasSupportForGridView, + getUpgradeStatus, + setUpgradeStatus, + incrementUpgradeStatusDisplayCount, }; diff --git a/assets/js/blocks/migration-products-to-product-collection/types.ts b/assets/js/blocks/migration-products-to-product-collection/types.ts new file mode 100644 index 00000000000..1fd795a6636 --- /dev/null +++ b/assets/js/blocks/migration-products-to-product-collection/types.ts @@ -0,0 +1,29 @@ +/** + * External dependencies + */ +import { type BlockInstance } from '@wordpress/blocks'; + +export type GetBlocksClientIds = ( blocks: BlockInstance[] ) => string[]; +export type IsBlockType = ( block: BlockInstance ) => boolean; +export type TransformBlock = ( + block: BlockInstance, + innerBlock: BlockInstance[] +) => BlockInstance; +export type ProductGridLayoutTypes = 'flex' | 'list'; +export type PostTemplateLayoutTypes = 'grid' | 'default'; + +export type ProductGridLayout = { + type: ProductGridLayoutTypes; + columns: number; +}; + +export type PostTemplateLayout = { + type: PostTemplateLayoutTypes; + columnCount: number; +}; +export type UpgradeNoticeStatuses = 'notseen' | 'seen' | 'reverted'; +export type UpgradeNoticeStatus = { + status: UpgradeNoticeStatuses; + time: number; + displayCount?: number; +}; diff --git a/assets/js/blocks/price-filter/edit.tsx b/assets/js/blocks/price-filter/edit.tsx index 637cdfd5853..25e3744a295 100644 --- a/assets/js/blocks/price-filter/edit.tsx +++ b/assets/js/blocks/price-filter/edit.tsx @@ -136,6 +136,7 @@ export default function ( { className="wc-block-price-slider__add-product-button" isSecondary href={ getAdminLink( 'post-new.php?post_type=product' ) } + target="_top" > { __( 'Add new product', 'woo-gutenberg-products-block' ) + ' ' } @@ -145,6 +146,7 @@ export default function ( { className="wc-block-price-slider__read_more_button" isTertiary href="https://docs.woocommerce.com/document/managing-products/" + target="_blank" > { __( 'Learn more', 'woo-gutenberg-products-block' ) } diff --git a/assets/js/blocks/product-collection/block.json b/assets/js/blocks/product-collection/block.json index 272a2cc89ac..12bbea73722 100644 --- a/assets/js/blocks/product-collection/block.json +++ b/assets/js/blocks/product-collection/block.json @@ -21,7 +21,7 @@ "displayLayout": { "type": "object" }, - "displayUpgradeNotice": { + "convertedFromProducts": { "type": "boolean", "default": false } diff --git a/assets/js/blocks/product-collection/inspector-controls/index.tsx b/assets/js/blocks/product-collection/inspector-controls/index.tsx index c75141c6113..0f7537fb219 100644 --- a/assets/js/blocks/product-collection/inspector-controls/index.tsx +++ b/assets/js/blocks/product-collection/inspector-controls/index.tsx @@ -1,14 +1,20 @@ /** * External dependencies */ -import type { ElementType } from 'react'; import type { BlockEditProps } from '@wordpress/blocks'; import { InspectorControls, BlockControls } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; -import { useMemo } from '@wordpress/element'; +import { type ElementType, useMemo } from '@wordpress/element'; import { EditorBlock } from '@woocommerce/types'; import { addFilter } from '@wordpress/hooks'; import { ProductCollectionFeedbackPrompt } from '@woocommerce/editor-components/feedback-prompt'; +import { + enableAutoUpdate, + revertMigration, + getUpgradeStatus, + HOURS_TO_DISPLAY_UPGRADE_NOTICE, + UPGRADE_NOTICE_DISPLAY_COUNT_THRESHOLD, +} from '@woocommerce/blocks/migration-products-to-product-collection'; import { // @ts-expect-error Using experimental features // eslint-disable-next-line @wordpress/no-unsafe-wp-apis @@ -34,7 +40,6 @@ import TaxonomyControls from './taxonomy-controls'; import HandPickedProductsControl from './hand-picked-products-control'; import AuthorControl from './author-control'; import DisplayLayoutControl from './display-layout-control'; -import { replaceProductCollectionWithProducts } from '../../shared/scripts'; const ProductCollectionInspectorControls = ( props: BlockEditProps< ProductCollectionAttributes > @@ -106,29 +111,92 @@ const ProductCollectionInspectorControls = ( export default ProductCollectionInspectorControls; -const isProductCollection = ( - block: EditorBlock< ProductCollectionAttributes > -) => block.name === metadata.name; +// Trigger Auto Upgrade of Products only once when module is loaded. +// This triggers subscription but only if: +// - auto update is enabled +// - user haven't reverted the migration +// - no other subscription is in place +enableAutoUpdate(); + +const isProductCollection = ( blockName: string ) => + blockName === metadata.name; + +const lessThanThresholdSinceUpdate = ( t: number ) => { + // Xh * 60m * 60s * 1000ms + const xHoursFromT = t + HOURS_TO_DISPLAY_UPGRADE_NOTICE * 60 * 60 * 1000; + return Date.now() < xHoursFromT; +}; + +const displayedLessThanThreshold = ( displayCount = 0 ) => { + return displayCount <= UPGRADE_NOTICE_DISPLAY_COUNT_THRESHOLD; +}; + +// Upgrade Notice should be displayed only if: +// - block is converted from Products +// - user haven't acknowledged seeing the notice +// - less than X hours since the notice was first displayed +// - notice was displayed less than X times +const shouldDisplayUpgradeNotice = ( props ) => { + const { attributes } = props; + const { convertedFromProducts } = attributes; + const { status, time, displayCount } = getUpgradeStatus(); + + return ( + convertedFromProducts && + status === 'notseen' && + lessThanThresholdSinceUpdate( time ) && + displayedLessThanThreshold( displayCount ) + ); +}; + +// Block should be unmarked as converted from Products if: +// block is converted from Products and either: +// - user acknowledged seeing the notice +// - it's more than X hours since the notice was first displayed +// - notice was displayed more than X times +// We do that to prevent showing the notice again after Products on +// other page were updated or local storage was cleared or user +// switched to another machine/browser etc. +const shouldBeUnmarkedAsConverted = ( props ) => { + const { attributes } = props; + const { convertedFromProducts } = attributes; + const { status, time, displayCount } = getUpgradeStatus(); + + return ( + convertedFromProducts && + ( status === 'seen' || + ! lessThanThresholdSinceUpdate( time ) || + ! displayedLessThanThreshold( displayCount ) ) + ); +}; export const withUpgradeNoticeControls = < T extends EditorBlock< T > >( BlockEdit: ElementType ) => - ( props: EditorBlock< ProductCollectionAttributes > ) => { - return isProductCollection( props ) ? ( + ( props: BlockEditProps< ProductCollectionAttributes > ) => { + if ( ! isProductCollection( props.name ) ) { + return ; + } + + const displayUpgradeNotice = shouldDisplayUpgradeNotice( props ); + const unmarkAsConverted = shouldBeUnmarkedAsConverted( props ); + + if ( unmarkAsConverted ) { + props.setAttributes( { convertedFromProducts: false } ); + } + + return ( <> - - { props.attributes.displayUpgradeNotice && ( - - ) } - + { displayUpgradeNotice && ( + + { + + } + + ) } - ) : ( - ); }; diff --git a/assets/js/blocks/product-collection/inspector-controls/upgrade-notice.tsx b/assets/js/blocks/product-collection/inspector-controls/upgrade-notice.tsx index d9fe5b9607e..df557e6a9f6 100644 --- a/assets/js/blocks/product-collection/inspector-controls/upgrade-notice.tsx +++ b/assets/js/blocks/product-collection/inspector-controls/upgrade-notice.tsx @@ -3,60 +3,81 @@ */ import { __ } from '@wordpress/i18n'; import { Notice, Button } from '@wordpress/components'; -import { BlockEditProps } from '@wordpress/blocks'; -import { createInterpolateElement } from '@wordpress/element'; +import { useLocalStorageState } from '@woocommerce/base-hooks'; +import { + createInterpolateElement, + useEffect, + useRef, +} from '@wordpress/element'; +import { + MIGRATION_STATUS_LS_KEY, + getInitialStatusLSValue, + incrementUpgradeStatusDisplayCount, +} from '@woocommerce/blocks/migration-products-to-product-collection'; -/** - * Internal dependencies - */ -import { ProductCollectionAttributes } from '../types'; - -const UpgradeNotice = ( - props: BlockEditProps< ProductCollectionAttributes > & { - revertMigration: () => void; - } -) => { - const { displayUpgradeNotice } = props.attributes; - const notice = createInterpolateElement( - __( - 'Products (Beta) block was upgraded to , an updated version with new features and simplified settings.', - 'woo-gutenberg-products-block' +const notice = createInterpolateElement( + __( + 'Products (Beta) block was upgraded to , an updated version with new features and simplified settings.', + 'woo-gutenberg-products-block' + ), + { + strongText: ( + + { __( `Product Collection`, 'woo-gutenberg-products-block' ) } + ), - { - strongText: ( - - { __( - `Product Collection`, - 'woo-gutenberg-products-block' - ) } - - ), - } - ); + } +); - const buttonLabel = __( - 'Revert to Products (Beta)', - 'woo-gutenberg-products-block' - ); +const buttonLabel = __( + 'Revert to Products (Beta)', + 'woo-gutenberg-products-block' +); + +type UpgradeNoticeProps = { + revertMigration: () => void; +}; + +const UpgradeNotice = ( { revertMigration }: UpgradeNoticeProps ) => { + const [ upgradeNoticeStatus, setUpgradeNoticeStatus ] = + useLocalStorageState( + MIGRATION_STATUS_LS_KEY, + getInitialStatusLSValue() + ); + + const canCountDisplays = useRef( true ); + const { status } = upgradeNoticeStatus; const handleRemove = () => { - // @todo: this logic needs to be extended to be hidden for all - // Product Collection blocks and whole store - props.setAttributes( { - displayUpgradeNotice: false, + setUpgradeNoticeStatus( { + status: 'seen', + time: Date.now(), } ); }; - const handleClick = () => { - props.revertMigration(); + const handleRevert = () => { + revertMigration(); }; - return displayUpgradeNotice ? ( + // Prevent the possibility to count displays multiple times when the + // block is selected and Inspector Controls are re-rendered multiple times. + useEffect( () => { + const countDisplay = () => { + if ( canCountDisplays.current ) { + incrementUpgradeStatusDisplayCount(); + canCountDisplays.current = false; + } + }; + + return countDisplay; + }, [ canCountDisplays ] ); + + return status === 'notseen' ? ( <>{ notice }

-
diff --git a/assets/js/blocks/product-collection/types.ts b/assets/js/blocks/product-collection/types.ts index 80fe339e80c..5d01793d5c7 100644 --- a/assets/js/blocks/product-collection/types.ts +++ b/assets/js/blocks/product-collection/types.ts @@ -14,7 +14,7 @@ export interface ProductCollectionAttributes { templateSlug: string; displayLayout: ProductCollectionDisplayLayout; tagName: string; - displayUpgradeNotice: boolean; + convertedFromProducts: boolean; } export interface ProductCollectionDisplayLayout { diff --git a/assets/js/blocks/product-gallery/icon.tsx b/assets/js/blocks/product-gallery/icon.tsx index 9fff759ec0d..a5587e33b3f 100644 --- a/assets/js/blocks/product-gallery/icon.tsx +++ b/assets/js/blocks/product-gallery/icon.tsx @@ -1,35 +1,34 @@ const Icon = () => ( - - + - - - - - - ); diff --git a/assets/js/blocks/product-query/constants.ts b/assets/js/blocks/product-query/constants.ts index e017460906e..a5d0fc5c8d4 100644 --- a/assets/js/blocks/product-query/constants.ts +++ b/assets/js/blocks/product-query/constants.ts @@ -13,9 +13,6 @@ import { VARIATION_NAME as PRODUCT_TITLE_ID } from './variations/elements/produc import { VARIATION_NAME as PRODUCT_TEMPLATE_ID } from './variations/elements/product-template'; import { ImageSizing } from '../../atomic/blocks/product-elements/image/types'; -export const AUTO_REPLACE_PRODUCTS_WITH_PRODUCT_COLLECTION = false; -export const MANUAL_REPLACE_PRODUCTS_WITH_PRODUCT_COLLECTION = false; - export const PRODUCT_QUERY_VARIATION_NAME = 'woocommerce/product-query'; export const EDIT_ATTRIBUTES_URL = diff --git a/assets/js/blocks/product-query/inspector-controls.tsx b/assets/js/blocks/product-query/inspector-controls.tsx index 05ea211b4f7..0ae16dd1287 100644 --- a/assets/js/blocks/product-query/inspector-controls.tsx +++ b/assets/js/blocks/product-query/inspector-controls.tsx @@ -1,15 +1,19 @@ /** * External dependencies */ -import type { ElementType } from 'react'; import { __ } from '@wordpress/i18n'; import { InspectorControls } from '@wordpress/block-editor'; -import { useSelect, subscribe } from '@wordpress/data'; +import { useSelect } from '@wordpress/data'; import { addFilter } from '@wordpress/hooks'; +import { type ElementType } from '@wordpress/element'; import { ProductQueryFeedbackPrompt } from '@woocommerce/editor-components/feedback-prompt'; import { EditorBlock, isNumber } from '@woocommerce/types'; import { usePrevious } from '@woocommerce/base-hooks'; -import { isWpVersion, getSettingWithCoercion } from '@woocommerce/settings'; +import { + manualUpdate, + MANUAL_REPLACE_PRODUCTS_WITH_PRODUCT_COLLECTION, +} from '@woocommerce/blocks/migration-products-to-product-collection'; +import { getSettingWithCoercion } from '@woocommerce/settings'; import { ProductQueryBlockQuery } from '@woocommerce/blocks/product-query/types'; import { FormTokenField, @@ -39,14 +43,11 @@ import { QUERY_DEFAULT_ATTRIBUTES, QUERY_LOOP_ID, STOCK_STATUS_OPTIONS, - AUTO_REPLACE_PRODUCTS_WITH_PRODUCT_COLLECTION, - MANUAL_REPLACE_PRODUCTS_WITH_PRODUCT_COLLECTION, } from './constants'; import { AttributesFilter } from './inspector-controls/attributes-filter'; import { PopularPresets } from './inspector-controls/popular-presets'; import { ProductSelector } from './inspector-controls/product-selector'; import { UpgradeNotice } from './inspector-controls/upgrade-notice'; -import { replaceProductsWithProductCollection } from '../shared/scripts'; import './editor.scss'; @@ -237,9 +238,7 @@ const ProductQueryControls = ( props: ProductQueryBlock ) => { <> { MANUAL_REPLACE_PRODUCTS_WITH_PRODUCT_COLLECTION && ( - + ) } { allowedControls?.includes( 'presets' ) && ( @@ -289,16 +288,3 @@ export const withProductQueryControls = }; addFilter( 'editor.BlockEdit', QUERY_LOOP_ID, withProductQueryControls ); - -if ( isWpVersion( '6.1', '>=' ) ) { - let unsubscribe: ( () => void ) | undefined; - if ( AUTO_REPLACE_PRODUCTS_WITH_PRODUCT_COLLECTION && ! unsubscribe ) { - unsubscribe = subscribe( () => { - replaceProductsWithProductCollection( () => { - if ( unsubscribe ) { - unsubscribe(); - } - } ); - }, 'core/block-editor' ); - } -} diff --git a/assets/js/blocks/product-tag/block.tsx b/assets/js/blocks/product-tag/block.tsx index 6dd138382d5..fb240d1382a 100644 --- a/assets/js/blocks/product-tag/block.tsx +++ b/assets/js/blocks/product-tag/block.tsx @@ -85,7 +85,6 @@ const ProductsByTagBlock = ( { ) } initialOpen={ ! attributes.tags.length && ! isEditing } > - { /* @ts-expect-error ProductTagControl is yet to be converted to tsx*/ } { @@ -203,16 +202,21 @@ const ProductsByTagBlock = ( { 'woo-gutenberg-products-block' ) }
- { /* @ts-expect-error ProductTagControl is yet to be converted to tsx*/ } { const ids = value.map( ( { id } ) => id ); - setChangedAttributes( { tags: ids } ); + setChangedAttributes( { + ...changedAttributes, + tags: ids, + } ); } } operator={ currentAttributes.tagOperator } onOperatorChange={ ( value = 'any' ) => - setChangedAttributes( { tagOperator: value } ) + setChangedAttributes( { + ...changedAttributes, + tagOperator: value, + } ) } /> diff --git a/assets/js/editor-components/product-tag-control/index.js b/assets/js/editor-components/product-tag-control/index.js deleted file mode 100644 index 4c00c169187..00000000000 --- a/assets/js/editor-components/product-tag-control/index.js +++ /dev/null @@ -1,213 +0,0 @@ -/** - * External dependencies - */ -import { __, _n, sprintf } from '@wordpress/i18n'; -import { Component } from '@wordpress/element'; -import { debounce } from '@woocommerce/base-utils'; -import PropTypes from 'prop-types'; -import { - SearchListControl, - SearchListItem, -} from '@woocommerce/editor-components/search-list-control'; -import { SelectControl } from '@wordpress/components'; -import { getSetting } from '@woocommerce/settings'; -import classNames from 'classnames'; - -/** - * Internal dependencies - */ -import { getProductTags } from '../utils'; -import './style.scss'; - -/** - * Component to handle searching and selecting product tags. - */ -class ProductTagControl extends Component { - constructor() { - super( ...arguments ); - this.state = { - list: [], - loading: true, - }; - this.renderItem = this.renderItem.bind( this ); - this.debouncedOnSearch = debounce( this.onSearch.bind( this ), 400 ); - } - - componentDidMount() { - const { selected } = this.props; - - getProductTags( { selected } ) - .then( ( list ) => { - this.setState( { list, loading: false } ); - } ) - .catch( () => { - this.setState( { list: [], loading: false } ); - } ); - } - - onSearch( search ) { - const { selected } = this.props; - this.setState( { loading: true } ); - - getProductTags( { selected, search } ) - .then( ( list ) => { - this.setState( { list, loading: false } ); - } ) - .catch( () => { - this.setState( { list: [], loading: false } ); - } ); - } - - renderItem( args ) { - const { item, search, depth = 0 } = args; - - const accessibleName = ! item.breadcrumbs.length - ? item.name - : `${ item.breadcrumbs.join( ', ' ) }, ${ item.name }`; - - return ( - 0, - 'is-skip-level': depth === 0 && item.parent !== 0, - } - ) } - { ...args } - aria-label={ sprintf( - /* translators: %1$d is the count of products, %2$s is the name of the tag. */ - _n( - '%1$d product tagged as %2$s', - '%1$d products tagged as %2$s', - item.count, - 'woo-gutenberg-products-block' - ), - item.count, - accessibleName - ) } - /> - ); - } - - render() { - const { list, loading } = this.state; - const { isCompact, onChange, onOperatorChange, operator, selected } = - this.props; - - const messages = { - clear: __( - 'Clear all product tags', - 'woo-gutenberg-products-block' - ), - list: __( 'Product Tags', 'woo-gutenberg-products-block' ), - noItems: __( - 'You have not set up any product tags on your store.', - 'woo-gutenberg-products-block' - ), - search: __( - 'Search for product tags', - 'woo-gutenberg-products-block' - ), - selected: ( n ) => - sprintf( - /* translators: %d is the count of selected tags. */ - _n( - '%d tag selected', - '%d tags selected', - n, - 'woo-gutenberg-products-block' - ), - n - ), - updated: __( - 'Tag search results updated.', - 'woo-gutenberg-products-block' - ), - }; - - const limitTags = getSetting( 'limitTags', false ); - - return ( - <> - - list.find( ( listItem ) => listItem.id === id ) - ) - .filter( Boolean ) } - onChange={ onChange } - onSearch={ limitTags ? this.debouncedOnSearch : null } - renderItem={ this.renderItem } - messages={ messages } - isCompact={ isCompact } - isHierarchical - /> - { !! onOperatorChange && ( - - ) } - - ); - } -} - -ProductTagControl.propTypes = { - /** - * Callback to update the selected product categories. - */ - onChange: PropTypes.func.isRequired, - /** - * Callback to update the category operator. If not passed in, setting is not used. - */ - onOperatorChange: PropTypes.func, - /** - * Setting for whether products should match all or any selected categories. - */ - operator: PropTypes.oneOf( [ 'all', 'any' ] ), - /** - * The list of currently selected tags. - */ - selected: PropTypes.array.isRequired, - isCompact: PropTypes.bool, -}; - -ProductTagControl.defaultProps = { - isCompact: false, - operator: 'any', -}; - -export default ProductTagControl; diff --git a/assets/js/editor-components/product-tag-control/index.tsx b/assets/js/editor-components/product-tag-control/index.tsx new file mode 100644 index 00000000000..9339dece00e --- /dev/null +++ b/assets/js/editor-components/product-tag-control/index.tsx @@ -0,0 +1,142 @@ +/** + * External dependencies + */ +import { __, _n, sprintf } from '@wordpress/i18n'; +import { useState, useEffect, useCallback, useMemo } from '@wordpress/element'; +import { SearchListControl } from '@woocommerce/editor-components/search-list-control'; +import { SelectControl } from '@wordpress/components'; +import { getSetting } from '@woocommerce/settings'; +import { useDebouncedCallback } from 'use-debounce'; + +/** + * Internal dependencies + */ +import type { SearchListItem as SearchListItemProps } from '../search-list-control/types'; +import ProductTagItem from './product-tag-item'; +import type { ProductTagControlProps } from './types'; +import { getProductTags } from '../utils'; +import './style.scss'; + +/** + * Component to handle searching and selecting product tags. + */ +const ProductTagControl = ( { + isCompact = false, + onChange, + onOperatorChange, + operator = 'any', + selected, +}: ProductTagControlProps ): JSX.Element => { + const [ list, setList ] = useState< SearchListItemProps[] >( [] ); + const [ loading, setLoading ] = useState( true ); + const [ isMounted, setIsMounted ] = useState( false ); + const limitTags = getSetting( 'limitTags', false ); + + const selectedTags = useMemo< SearchListItemProps[] >( () => { + return list.filter( ( item ) => selected.includes( item.id ) ); + }, [ list, selected ] ); + + const onSearch = useCallback( + ( search: string ) => { + setLoading( true ); + getProductTags( { selected, search } ) + .then( ( newList ) => { + setList( newList ); + setLoading( false ); + } ) + .catch( () => { + setLoading( false ); + } ); + }, + [ selected ] + ); + + // Load on mount. + useEffect( () => { + if ( isMounted ) { + return; + } + onSearch( '' ); + setIsMounted( true ); + }, [ onSearch, isMounted ] ); + + const debouncedOnSearch = useDebouncedCallback( onSearch, 400 ); + + const messages = { + clear: __( 'Clear all product tags', 'woo-gutenberg-products-block' ), + list: __( 'Product Tags', 'woo-gutenberg-products-block' ), + noItems: __( + 'You have not set up any product tags on your store.', + 'woo-gutenberg-products-block' + ), + search: __( 'Search for product tags', 'woo-gutenberg-products-block' ), + selected: ( n: number ) => + sprintf( + /* translators: %d is the count of selected tags. */ + _n( + '%d tag selected', + '%d tags selected', + n, + 'woo-gutenberg-products-block' + ), + n + ), + updated: __( + 'Tag search results updated.', + 'woo-gutenberg-products-block' + ), + }; + + return ( + <> + + { !! onOperatorChange && ( + + ) } + + ); +}; + +export default ProductTagControl; diff --git a/assets/js/editor-components/product-tag-control/product-tag-item.tsx b/assets/js/editor-components/product-tag-control/product-tag-item.tsx new file mode 100644 index 00000000000..db656cbcd1e --- /dev/null +++ b/assets/js/editor-components/product-tag-control/product-tag-item.tsx @@ -0,0 +1,52 @@ +/** + * External dependencies + */ +import { _n, sprintf } from '@wordpress/i18n'; +import { SearchListItem } from '@woocommerce/editor-components/search-list-control'; +import classNames from 'classnames'; + +/** + * Internal dependencies + */ +import type { renderItemArgs } from '../search-list-control/types'; + +export const ProductTagItem = ( { + item, + search, + depth = 0, + ...rest +}: renderItemArgs ): JSX.Element => { + const accessibleName = ! item.breadcrumbs.length + ? item.name + : `${ item.breadcrumbs.join( ', ' ) }, ${ item.name }`; + + return ( + 0, + 'is-skip-level': depth === 0 && item.parent !== 0, + } + ) } + item={ item } + search={ search } + depth={ depth } + { ...rest } + ariaLabel={ sprintf( + /* translators: %1$d is the count of products, %2$s is the name of the tag. */ + _n( + '%1$d product tagged as %2$s', + '%1$d products tagged as %2$s', + item.count, + 'woo-gutenberg-products-block' + ), + item.count, + accessibleName + ) } + /> + ); +}; + +export default ProductTagItem; diff --git a/assets/js/editor-components/product-tag-control/types.ts b/assets/js/editor-components/product-tag-control/types.ts new file mode 100644 index 00000000000..3004048bcd4 --- /dev/null +++ b/assets/js/editor-components/product-tag-control/types.ts @@ -0,0 +1,13 @@ +/** + * Internal dependencies + */ +import type { SearchListItem as SearchListItemProps } from '../search-list-control/types'; + +export type ProductTagControlProps = { + isCompact?: boolean; + onChange: ( selected: SearchListItemProps[] ) => void; + onOperatorChange?: ( operator: string ) => void; + operator?: string; + // Selected tag ids. + selected: ( number | string )[]; +}; diff --git a/assets/js/editor-components/search-list-control/types.ts b/assets/js/editor-components/search-list-control/types.ts index b7852a251ac..043a1848c07 100644 --- a/assets/js/editor-components/search-list-control/types.ts +++ b/assets/js/editor-components/search-list-control/types.ts @@ -90,6 +90,8 @@ export interface renderItemArgs extends ItemProps { * If not provided, a default name will be generated using the controlId. */ name?: string; + // Aria label for the input. If not provided, a default label will be generated using the item name. + ariaLabel?: string; } export interface SearchListControlProps { @@ -110,7 +112,7 @@ export interface SearchListControlProps { // Callback fired when selected items change, whether added, cleared, or removed. Passed an array of item objects (as passed in via props.list). onChange: ( search: SearchListItem[] ) => void; // Callback fired when the search field is used. - onSearch?: ( search: string ) => void; + onSearch?: ( ( search: string ) => void ) | undefined; // Callback to render each item in the selection list, allows any custom object-type rendering. renderItem?: ( args: renderItemArgs ) => JSX.Element; // The list of currently selected items. diff --git a/assets/js/interactivity/index.js b/assets/js/interactivity/index.js index 67d56e8e9cb..00008625110 100644 --- a/assets/js/interactivity/index.js +++ b/assets/js/interactivity/index.js @@ -1,7 +1,9 @@ import registerDirectives from './directives'; import { init } from './router'; -export { store } from './store'; +import { rawStore, afterLoads } from './store'; + export { navigate } from './router'; +export { store } from './store'; /** * Initialize the Interactivity API. @@ -9,6 +11,7 @@ export { navigate } from './router'; document.addEventListener( 'DOMContentLoaded', async () => { registerDirectives(); await init(); + afterLoads.forEach( ( afterLoad ) => afterLoad( rawStore ) ); // eslint-disable-next-line no-console console.log( 'Interactivity API started' ); } ); diff --git a/assets/js/interactivity/store.js b/assets/js/interactivity/store.js index d00308f8012..de5c59db069 100644 --- a/assets/js/interactivity/store.js +++ b/assets/js/interactivity/store.js @@ -32,12 +32,15 @@ const getSerializedState = () => { return {}; }; +export const afterLoads = new Set(); + const rawState = getSerializedState(); export const rawStore = { state: deepSignal( rawState ) }; if ( typeof window !== 'undefined' ) window.store = rawStore; -export const store = ( { state, ...block } ) => { +export const store = ( { state, ...block }, { afterLoad } = {} ) => { deepMerge( rawStore, block ); deepMerge( rawState, state ); + if ( afterLoad ) afterLoads.add( afterLoad ); }; diff --git a/bin/webpack-configs.js b/bin/webpack-configs.js index 3dd7a25347d..84de696091a 100644 --- a/bin/webpack-configs.js +++ b/bin/webpack-configs.js @@ -733,7 +733,7 @@ const getSiteEditorConfig = ( options = {} ) => { * @param {Object} options Build options. */ const getStylingConfig = ( options = {} ) => { - let { fileSuffix } = options; + let { fileSuffix, isClassicThemeConfig } = options; const { alias, resolvePlugins = [] } = options; fileSuffix = fileSuffix ? `-${ fileSuffix }` : ''; const resolve = alias @@ -775,11 +775,58 @@ const getStylingConfig = ( options = {} ) => { chunks: 'all', priority: 10, }, + ...( isClassicThemeConfig && { + vendorsStyle: { + test: /[\/\\]node_modules[\/\\].*?style\.s?css$/, + name: 'wc-blocks-vendors-style', + chunks: 'all', + priority: 7, + }, + blocksStyle: { + // Capture all stylesheets with name `style` or name that starts with underscore (abstracts). + test: /(style|_.*)\.scss$/, + name: 'wc-all-blocks-style', + chunks: 'all', + priority: 5, + }, + } ), }, }, }, module: { rules: [ + { + test: /[\/\\]node_modules[\/\\].*?style\.s?css$/, + use: [ + MiniCssExtractPlugin.loader, + { loader: 'css-loader', options: { importLoaders: 1 } }, + 'postcss-loader', + { + loader: 'sass-loader', + options: { + sassOptions: { + includePaths: [ 'node_modules' ], + }, + additionalData: ( content ) => { + const styleImports = [ + 'colors', + 'breakpoints', + 'variables', + 'mixins', + 'animations', + 'z-index', + ] + .map( + ( imported ) => + `@import "~@wordpress/base-styles/${ imported }";` + ) + .join( ' ' ); + return styleImports + content; + }, + }, + }, + ], + }, { test: /\.(j|t)sx?$/, use: { @@ -800,6 +847,7 @@ const getStylingConfig = ( options = {} ) => { }, { test: /\.s?css$/, + exclude: /node_modules/, use: [ MiniCssExtractPlugin.loader, 'css-loader', diff --git a/bin/webpack-entries.js b/bin/webpack-entries.js index e7d0f748454..b2383206ce0 100644 --- a/bin/webpack-entries.js +++ b/bin/webpack-entries.js @@ -114,6 +114,16 @@ const getBlockEntries = ( relativePath ) => { const entries = { styling: { + // @wordpress/components styles + 'custom-select-control-style': + './node_modules/wordpress-components/src/custom-select-control/style.scss', + 'snackbar-notice-style': + './node_modules/wordpress-components/src/snackbar/style.scss', + 'combobox-control-style': + './node_modules/wordpress-components/src/combobox-control/style.scss', + 'form-token-field-style': + './node_modules/wordpress-components/src/form-token-field/style.scss', + // Packages styles 'packages-style': glob.sync( './packages/**/index.js' ), @@ -157,6 +167,8 @@ const entries = { ...getBlockEntries( 'frontend.{t,j}s{,x}' ), 'mini-cart-component': './assets/js/blocks/mini-cart/component-frontend.tsx', + 'product-button-interactivity': + './assets/js/atomic/blocks/product-elements/button/frontend.tsx', }, payments: { 'wc-payment-method-cheque': diff --git a/bin/wp-env-with-wp-622.js b/bin/wp-env-with-wp-622.js new file mode 100755 index 00000000000..b27fbfcb2e9 --- /dev/null +++ b/bin/wp-env-with-wp-622.js @@ -0,0 +1,19 @@ +const fs = require( 'fs' ); +const path = require( 'path' ); + +const wpEnvRaw = fs.readFileSync( + path.join( __dirname, '..', '.wp-env.json' ) +); +const wpEnv = JSON.parse( wpEnvRaw ); + +// Pin the core version to 6.2.2 for Jest E2E test so we can keep the test +// passing when new WordPress versions are released. We do this because we're +// moving to Playwright and will abandon the Jest E2E tests once the migration +// is complete. +wpEnv.core = 'WordPress/WordPress#6.2.2'; + +// We write the new file to .wp-env.override.json (https://developer.wordpress.org/block-editor/reference-guides/packages/packages-env/#wp-env-override-json) +fs.writeFileSync( + path.join( __dirname, '..', '.wp-env.override.json' ), + JSON.stringify( wpEnv ) +); diff --git a/composer.json b/composer.json index d2c6654610e..1f30a59f997 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "WooCommerce blocks for the Gutenberg editor.", "homepage": "https://woocommerce.com/", "type": "wordpress-plugin", - "version": "10.9.0-dev", + "version": "11.0.0-dev", "keywords": [ "gutenberg", "woocommerce", diff --git a/docs/internal-developers/testing/releases/1083.md b/docs/internal-developers/testing/releases/1083.md new file mode 100644 index 00000000000..6f7a226df45 --- /dev/null +++ b/docs/internal-developers/testing/releases/1083.md @@ -0,0 +1,24 @@ +# Testing notes and ZIP for release 10.8.3 + +Zip file for testing: [woocommerce-gutenberg-products-block.zip](https://github.com/woocommerce/woocommerce-blocks/files/12322309/woocommerce-gutenberg-products-block.zip) + +## WooCommerce Core + +### Create wc-all-block-styles chunk with all blocks stylesheet for classic themes. [#10543](https://github.com/woocommerce/woocommerce-blocks/pull/10543) + +⚠️: Following these testing instruction for a classic theme (Storefront) and a block theme (TT3) + +#### For classic theme ensure that is loaded only `wc-all-blocks-style.css` and `wc-blocks-vendors-style.css` stylesheets + +#### For block theme sure that is loaded only the stylesheets of blocks visible in the page + +1. Create a post or page and add the All Products block. Verify styles are loaded correctly. +2. Visit the page in the frontend and verify styles are loaded correctly in the frontend as well. +3. Repeat steps 1 and 2 with all blocks listed on [this page](https://wordpress.org/plugins/woo-gutenberg-products-block/). Make sure to test each block individually. So, when possible, try with only one block on the page (in some cases, that's not possible, ie: filter blocks, in that case, try with as few blocks as possible on the page). The reason is that we want to make sure each block includes the style dependencies that it needs, so they need to be tested in isolation, otherwise styles from other blocks might leak into other blocks and "help fix issues". + +### Fix the "On Sale" badge position. [#10550](https://github.com/woocommerce/woocommerce-blocks/pull/10550) + +1. Enable the `Storefront` theme. +2. Create a new page or post. +3. Add the `Products by Attribute`, `Products by Tag`, `Products by Category`, `Handpicked products` and `All products` blocks. +4. Check the `Sale` back shows on the top-right corner of the image on all of them, in the editor and in the frontend. diff --git a/docs/internal-developers/testing/releases/1090.md b/docs/internal-developers/testing/releases/1090.md new file mode 100644 index 00000000000..6c530e6fd2e --- /dev/null +++ b/docs/internal-developers/testing/releases/1090.md @@ -0,0 +1,196 @@ +# Testing notes and ZIP for release 10.9.0 + +Zip file for testing: [woocommerce-gutenberg-products-block.zip](https://github.com/woocommerce/woocommerce-blocks/files/12343048/woocommerce-gutenberg-products-block.zip) + +## WooCommerce Core + +### Enhancements + +#### Add the `wc-blocks-footer-pattern` class identifier to all footer patterns. [#10542](https://github.com/woocommerce/woocommerce-blocks/pull/10542) + +1. Create a new post +2. Insert the following footer patterns to the post: + +- Large Footer +- Large Footer Dark +- Simple Footer +- Simple Footer Dark +- Footer with Simple Menu and Cart +- Footer with 2 Menus +- Footer with 2 Menus Dark +- Footer with 3 Menus + +3. Open the code editor and make sure you can see the `wc-blocks-footer-pattern` class in all footer patterns: + +Screenshot 2023-08-11 at 11 16 38 + +4. No changes should be observed by the end users: all patterns listed above should work as expected both on the editor side and on the front end. + +#### Add the `wc-blocks-header-pattern` class identifier to all header patterns. [#10541](https://github.com/woocommerce/woocommerce-blocks/pull/10541) + +1. Create a new post +2. Insert the following header patterns to the post: + +- Centered Header Menu with Search +- Essential Header +- Essential Header Dark +- Large Header +- Large Header Dark +- Minimal Header + +3. Open the code editor and make sure you can see the `wc-blocks-header-pattern` class in all header patterns: + +Screenshot 2023-08-11 at 10 55 27 + +4. No changes should be observed by the end users: all patterns listed above should work as expected both on the editor side and on the front end. + +#### Featured Products: Fresh & Tasty pattern: Enhance mobile view and optimize images. [#10521](https://github.com/woocommerce/woocommerce-blocks/pull/10521) + +1. Create a new post +2. Insert the **Featured Products: Fresh & Tasty** pattern +3. Make sure the pattern is properly displayed in the editor without any problems +4. Save the post and head over to the front-end +5. Make sure the pattern is correctly displayed and the mobile view matches the screenshot provided on the description of this PR. + +##### Screenshots + + + + + + +
Before: +

+Screenshot 2023-08-09 at 18 11 40 +
After: +

+Screenshot 2023-08-09 at 18 11 57 +
+ +#### Add placeholder images and update text styles for the Alternating Image and Text pattern. [#10479](https://github.com/woocommerce/woocommerce-blocks/pull/10479) + +1. Create a new post. +2. Insert the **Alternating Image and Text** pattern. +3. Save the post and ensure everything is working as expected both in the editor and on the frontend. The designs should match the "After" screenshot on this PR. + +##### Screenshots + + + + + + +
Before: +

+Screenshot 2023-08-04 at 15 57 46 +
After: +

+Screenshot 2023-08-04 at 16 02 36 +
+ +#### Performance: Selecting shipping rates during checkout, and API calls in general, are now faster. Shipping selection improved by at least 1 second. [#10472](https://github.com/woocommerce/woocommerce-blocks/pull/10472) + +1. Add items to the cart and head to checkout. +2. Enter your address. Ensure your store has a few shipping methods to choose from. +3. Select a shipping rate. Confirm the UI is blocked (cursor change, faded out radio button). +4. Confirm shipping totals etc update successfully. + +#### Checkout: Prevent postcode validation error notice appearing after pushing changes for other fields. [#10315](https://github.com/woocommerce/woocommerce-blocks/pull/10315) + +1. Add some items to the cart and go to the checkout page +2. Fill out your shipping address and ensure rates appear. You need to complete all required fields before this occurs. +3. Type into a shipping address field. After a short delay confirm that the shipping rates refresh. +4. Type into a different field. Without waiting, tab out of the field. The refresh should happen immediately. +5. Interact with the postcode + country fields. + - Choose Algeria. Enter invalid postcode e.g. ABC. Notice the inline validation. + - Choose Albania. Inline validation should disappear. The postcode is now valid. + - Go back to Algeria. The postcode will clear. There should be no validation message visible. + - Place order. Empty postcode field now has a validation message. +6. Correct all errors and successfully place the order. + +#### Product Button block: Integrate Interactivity API to make the block dynamically. [#10006](https://github.com/woocommerce/woocommerce-blocks/pull/10006) + +1. Ensure that you are using the Blockified Product Catalog Template. If not, enable it. +2. Open the Site Editor and add the Mini Cart Block on the header. Save. +3. Visit the Product Catalog (`/shop`). +4. Click the button to ensure the product is added to the cart. +5. Ensure that the transition Add to cart -> loading status -> 1 in the cart is smooth. +6. Click on the Mini Cart. +7. Edit the quantity of the product. +8. Ensure that the changes are reflected in the Product Button. +9. Via dev tools ensure that the button element has the class `added` when the product is in the cart, and the button element has the class `loading` while the product is added to the cart. + +##### Test ProductButton with cache plugin + +1. Install the plugin [WP Optimize](https://wordpress.org/plugins/wp-optimize/). +2. Ensure that your cart is empty. +3. Ensure that you have the Mini Cart block in the header. +4. Enable the cache. +5. Visit the page with the Products block. +6. Refresh the page. +7. Add a product. +8. Refresh the page. +9. Ensure that the counter is updated with a smoother animation. +10. Open the Mini Cart block and change the quantity of the product in the cart. +11. Ensure that the counter inside the button is updated with a smoother animation. +12. Disable the plugin. +13. Visit the page with the Products block. +14. Open the Mini Cart block and change the quantity of the product in the cart. +15. Ensure that the counter inside the button is updated with a smoother animation. +16. Ensure that there isn't any regression. + +#### Interactivity API: Update interactive regions during client-side navigation [#10200](https://github.com/woocommerce/woocommerce-blocks/pull/10200) + +1. Ensure that you are using the Blockified Product Catalog Template. If not, enable it. +2. Open the Site Editor and go to the Product Catalog Template. +3. In order to make pagination more noticeable, in the Product query block, disable the inherited query settings and manually modify the block markup to set the `perPage` prop inside the `query` attribute to `1`. +4. Visit the Product Catalog (`/shop`). +5. Click on any link inside the Pagination block. +6. Via dev tools, ensure that only the HTML inside the Query block is updated. +7. Navigate back and forward. +8. Via dev tools, ensure that only the HTML inside the Query block is updated. +9. Click on any link outside the Pagination block +10. Ensure that the browser navigates to the selected link. + +##### Screenshots + + + + + + +
Before: +

+ +
After: +

+ +
+ +### Bug Fixes + +#### Fix Express Payments buttons display in the Cart block. [#10534](https://github.com/woocommerce/woocommerce-blocks/pull/10534) + +1. activate Stripe and PayPal and make sure they are active +2. add a product to cart +3. visit the Cart block and make sure buttons are stacked +4. go to the Checkout block and make sure buttons are in a grid +5. repeat the testing in the editor as well +6. repeat the steps with different screen sizes +7. Mini cart does not display express payments so it does not need testing + +#### Fixed a bug which caused theme border colors to not correctly show on the blocks on the editor side. [#10468](https://github.com/woocommerce/woocommerce-blocks/pull/10468) + +1. Add a “Featured Category” block to your page. +2. Open the Inspector Controls → Styles tab → Add a border. +3. Select a custom width and select a color from the theme colors. +4. Ensure this color is shown correctly in the editor. +5. [Regression test] Ensure custom colors are shown correctly in the editor. +6. [Regression test] Ensure both are shown in the front-end. + +#### Fix an issue where inner Cart blocks were incorrectly nested. [#10447](https://github.com/woocommerce/woocommerce-blocks/pull/10447) + +1. Enable a block theme. +2. Go to Appearance -> Editor -> Templates -> Cart. +3. Edit the template and click the "Cart" button at the top of the editor - a dropdown will appear. Click "Clear customizations" here (If it does not show then proceed anyway) +4. Open the list view of blocks, and expand the Cart block, and the Empty Cart block. Ensure there are no duplicate Empty/Filled cart blocks in the top-level Empty Cart block. diff --git a/docs/internal-developers/testing/releases/README.md b/docs/internal-developers/testing/releases/README.md index 4789b58035e..819ac0e0d30 100644 --- a/docs/internal-developers/testing/releases/README.md +++ b/docs/internal-developers/testing/releases/README.md @@ -168,7 +168,8 @@ Every release includes specific testing instructions for new features and bug fi - [10.8.0](./1080.md) - [10.8.1](./1081.md) - [10.8.2](./1082.md) - + - [10.8.3](./1083.md) +- [10.9.0](./1090.md) diff --git a/images/pattern-placeholders/crafting-pots.png b/images/pattern-placeholders/crafting-pots.png new file mode 100644 index 00000000000..2846b25919f Binary files /dev/null and b/images/pattern-placeholders/crafting-pots.png differ diff --git a/images/pattern-placeholders/fresh-lettuce-washed.png b/images/pattern-placeholders/fresh-lettuce-washed.png index 0611666d868..b6c8b432d83 100644 Binary files a/images/pattern-placeholders/fresh-lettuce-washed.png and b/images/pattern-placeholders/fresh-lettuce-washed.png differ diff --git a/images/pattern-placeholders/fresh-organic-tomatoes.png b/images/pattern-placeholders/fresh-organic-tomatoes.png index 7f329428d62..1d00fbda7bc 100644 Binary files a/images/pattern-placeholders/fresh-organic-tomatoes.png and b/images/pattern-placeholders/fresh-organic-tomatoes.png differ diff --git a/images/pattern-placeholders/hand-made-pots.png b/images/pattern-placeholders/hand-made-pots.png new file mode 100644 index 00000000000..e380d933618 Binary files /dev/null and b/images/pattern-placeholders/hand-made-pots.png differ diff --git a/images/pattern-placeholders/russet-organic-potatoes.png b/images/pattern-placeholders/russet-organic-potatoes.png index 58b87e8e2fb..acdcd7f4f40 100644 Binary files a/images/pattern-placeholders/russet-organic-potatoes.png and b/images/pattern-placeholders/russet-organic-potatoes.png differ diff --git a/images/pattern-placeholders/sweet-organic-lemons.png b/images/pattern-placeholders/sweet-organic-lemons.png index 85771519168..2a7ea0aded9 100644 Binary files a/images/pattern-placeholders/sweet-organic-lemons.png and b/images/pattern-placeholders/sweet-organic-lemons.png differ diff --git a/package-lock.json b/package-lock.json index 3d8550d049f..5088a4b4787 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@woocommerce/block-library", - "version": "10.9.0-dev", + "version": "11.0.0-dev", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@woocommerce/block-library", - "version": "10.9.0-dev", + "version": "11.0.0-dev", "hasInstallScript": true, "license": "GPL-3.0+", "dependencies": { @@ -32,7 +32,7 @@ "compare-versions": "4.1.3", "config": "3.3.7", "dataloader": "2.1.0", - "deepsignal": "^1.1.0", + "deepsignal": "1.3.6", "dinero.js": "1.9.1", "dompurify": "^2.4.0", "downshift": "6.1.7", @@ -115,7 +115,7 @@ "@wordpress/dependency-extraction-webpack-plugin": "3.2.1", "@wordpress/dom": "3.27.0", "@wordpress/e2e-test-utils": "10.1.0", - "@wordpress/e2e-test-utils-playwright": "https://github.com/woocommerce/woocommerce-blocks/files/11286971/wordpress-e2e-test-utils-playwright-0.0.0.tgz", + "@wordpress/e2e-test-utils-playwright": "0.6.0", "@wordpress/e2e-tests": "^4.6.0", "@wordpress/element": "4.20.0", "@wordpress/env": "7.0.0", @@ -126,6 +126,7 @@ "@wordpress/scripts": "24.6.0", "autoprefixer": "10.4.14", "axios": "0.27.2", + "babel-jest": "^29.6.2", "babel-plugin-explicit-exports-references": "^1.0.2", "babel-plugin-react-docgen": "4.2.1", "babel-plugin-transform-async-generator-functions": "6.24.1", @@ -15391,9 +15392,9 @@ } }, "node_modules/@wordpress/e2e-test-utils-playwright": { - "version": "0.0.0", - "resolved": "https://github.com/woocommerce/woocommerce-blocks/files/11286971/wordpress-e2e-test-utils-playwright-0.0.0.tgz", - "integrity": "sha512-BLtq8Vcsyxbac1d8I+klE2Zrhbso3DJ2YTRl3ZmZ6KMiIiYWCvPLLIbtYAWiIDy6NU2jiT1wLlJ4G4jyV3DhIQ==", + "version": "0.6.0", + "resolved": "https://github.com/woocommerce/woocommerce-blocks/files/12309320/wordpress-e2e-test-utils-playwright-0.6.0.tgz", + "integrity": "sha512-5lo8iRuRfUOAPMRsQteSkKyMoP1IHHu6GKye8Df+YEPz1LWXcS6YRFGI3HrONBpcaHLrJC7LVav4UdB/sBPqUw==", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -16127,6 +16128,68 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@wordpress/e2e-tests/node_modules/babel-jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", + "integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==", + "dev": true, + "dependencies": { + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^27.5.1", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/@wordpress/e2e-tests/node_modules/babel-jest/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@wordpress/e2e-tests/node_modules/babel-plugin-jest-hoist": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz", + "integrity": "sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.0.0", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@wordpress/e2e-tests/node_modules/babel-preset-jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz", + "integrity": "sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^27.5.1", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@wordpress/e2e-tests/node_modules/ci-info": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.2.tgz", @@ -17389,6 +17452,68 @@ "react-dom": "^17.0.0" } }, + "node_modules/@wordpress/jest-preset-default/node_modules/babel-jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", + "integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==", + "dev": true, + "dependencies": { + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^27.5.1", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/@wordpress/jest-preset-default/node_modules/babel-plugin-jest-hoist": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz", + "integrity": "sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.0.0", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@wordpress/jest-preset-default/node_modules/babel-preset-jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz", + "integrity": "sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^27.5.1", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@wordpress/jest-preset-default/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/@wordpress/jest-puppeteer-axe": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@wordpress/jest-puppeteer-axe/-/jest-puppeteer-axe-4.0.2.tgz", @@ -18297,6 +18422,68 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@wordpress/scripts/node_modules/babel-jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", + "integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==", + "dev": true, + "dependencies": { + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^27.5.1", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/@wordpress/scripts/node_modules/babel-jest/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@wordpress/scripts/node_modules/babel-plugin-jest-hoist": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz", + "integrity": "sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.0.0", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@wordpress/scripts/node_modules/babel-preset-jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz", + "integrity": "sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^27.5.1", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@wordpress/scripts/node_modules/ci-info": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz", @@ -21004,27 +21191,150 @@ } }, "node_modules/babel-jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", - "integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.6.2.tgz", + "integrity": "sha512-BYCzImLos6J3BH/+HvUCHG1dTf2MzmAB4jaVxHV+29RZLjR29XuYTmsf2sdDwkrb+FczkGo3kOhE7ga6sI0P4A==", "dev": true, "dependencies": { - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", + "@jest/transform": "^29.6.2", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^27.5.1", + "babel-preset-jest": "^29.5.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "@babel/core": "^7.8.0" } }, + "node_modules/babel-jest/node_modules/@jest/transform": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.6.2.tgz", + "integrity": "sha512-ZqCqEISr58Ce3U+buNFJYUktLJZOggfyvR+bZMaiV1e8B1SIvJbwZMrYz3gx/KAPn9EXmOmN+uB08yLCjWkQQg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.1", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.6.2", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.6.2", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-jest/node_modules/@jest/types": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.1.tgz", + "integrity": "sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-jest/node_modules/@types/yargs": { + "version": "17.0.24", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", + "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/babel-jest/node_modules/ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-jest/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/babel-jest/node_modules/jest-haste-map": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.6.2.tgz", + "integrity": "sha512-+51XleTDAAysvU8rT6AnS1ZJ+WHVNqhj1k6nTvN2PYP+HjU3kqlaKQ1Lnw3NYW3bm2r8vq82X0Z1nDDHZMzHVA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.1", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.6.2", + "jest-worker": "^29.6.2", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/babel-jest/node_modules/jest-regex-util": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", + "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-jest/node_modules/jest-util": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.2.tgz", + "integrity": "sha512-3eX1qb6L88lJNCFlEADKOkjpXJQyZRiavX1INZ4tRnrBVr2COd3RgcTLyUiEXMNBlDU/cgYq6taUS0fExrWW4w==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/babel-jest/node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -21034,6 +21344,19 @@ "node": ">=8" } }, + "node_modules/babel-jest/node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, "node_modules/babel-loader": { "version": "8.2.5", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.5.tgz", @@ -21265,18 +21588,18 @@ } }, "node_modules/babel-plugin-jest-hoist": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz", - "integrity": "sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==", + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", + "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", "dev": true, "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", + "@types/babel__core": "^7.1.14", "@types/babel__traverse": "^7.0.6" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/babel-plugin-macros": { @@ -21498,16 +21821,16 @@ } }, "node_modules/babel-preset-jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz", - "integrity": "sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==", + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", + "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", "dev": true, "dependencies": { - "babel-plugin-jest-hoist": "^27.5.1", + "babel-plugin-jest-hoist": "^29.5.0", "babel-preset-current-node-syntax": "^1.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "@babel/core": "^7.0.0" @@ -25874,15 +26197,28 @@ } }, "node_modules/deepsignal": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/deepsignal/-/deepsignal-1.1.1.tgz", - "integrity": "sha512-ZcRi5KogA7hQiJcnUDGrDno7OCwfyBdcP96yf4BHF3JsUc8SRt3w24RZX6L0Ejijnol8Jt5cV5X8h0dotixHHA==", - "dependencies": { - "@preact/signals": "^1.0.0", - "@preact/signals-core": "^1.0.0" - }, + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/deepsignal/-/deepsignal-1.3.6.tgz", + "integrity": "sha512-yjd+vtiznL6YaMptOsKnEKkPr60OEApa+LRe+Qe6Ile/RfCOrELKk/YM3qVpXFZiyOI3Ng67GDEyjAlqVc697g==", "peerDependencies": { - "preact": "10.x" + "@preact/signals": "^1.1.4", + "@preact/signals-core": "^1.3.1", + "@preact/signals-react": "^1.3.3", + "preact": "^10.16.0" + }, + "peerDependenciesMeta": { + "@preact/signals": { + "optional": true + }, + "@preact/signals-core": { + "optional": true + }, + "@preact/signals-react": { + "optional": true + }, + "preact": { + "optional": true + } } }, "node_modules/default-browser-id": { @@ -33603,6 +33939,59 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, + "node_modules/jest-cli/node_modules/babel-jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", + "integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==", + "dev": true, + "dependencies": { + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^27.5.1", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/jest-cli/node_modules/babel-plugin-jest-hoist": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz", + "integrity": "sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.0.0", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-cli/node_modules/babel-preset-jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz", + "integrity": "sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^27.5.1", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/jest-cli/node_modules/ci-info": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.1.tgz", @@ -34212,58 +34601,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-config/node_modules/babel-jest": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz", - "integrity": "sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==", - "dev": true, - "dependencies": { - "@jest/transform": "^29.5.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.5.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/jest-config/node_modules/babel-plugin-jest-hoist": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", - "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", - "dev": true, - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-config/node_modules/babel-preset-jest": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", - "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", - "dev": true, - "dependencies": { - "babel-plugin-jest-hoist": "^29.5.0", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, "node_modules/jest-config/node_modules/ci-info": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", @@ -36664,13 +37001,13 @@ } }, "node_modules/jest-worker": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", - "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.6.2.tgz", + "integrity": "sha512-l3ccBOabTdkng8I/ORCkADz4eSMKejTYv1vB/Z83UiubqhC1oQ5Li6dWCyqOIvSifGjUBxuvxvlm6KGK2DtuAQ==", "dev": true, "dependencies": { "@types/node": "*", - "jest-util": "^29.5.0", + "jest-util": "^29.6.2", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, @@ -36679,12 +37016,12 @@ } }, "node_modules/jest-worker/node_modules/@jest/types": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz", - "integrity": "sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.1.tgz", + "integrity": "sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw==", "dev": true, "dependencies": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.0", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -36720,12 +37057,12 @@ } }, "node_modules/jest-worker/node_modules/jest-util": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", - "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.2.tgz", + "integrity": "sha512-3eX1qb6L88lJNCFlEADKOkjpXJQyZRiavX1INZ4tRnrBVr2COd3RgcTLyUiEXMNBlDU/cgYq6taUS0fExrWW4w==", "dev": true, "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.1", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -43935,9 +44272,9 @@ "dev": true }, "node_modules/preact": { - "version": "10.11.3", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", - "integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==", + "version": "10.16.0", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.16.0.tgz", + "integrity": "sha512-XTSj3dJ4roKIC93pald6rWuB2qQJO9gO2iLLyTe87MrjQN+HklueLsmskbywEWqCHlclgz3/M4YLL2iBr9UmMA==", "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -65592,8 +65929,9 @@ } }, "@wordpress/e2e-test-utils-playwright": { - "version": "https://github.com/woocommerce/woocommerce-blocks/files/11286971/wordpress-e2e-test-utils-playwright-0.0.0.tgz", - "integrity": "sha512-BLtq8Vcsyxbac1d8I+klE2Zrhbso3DJ2YTRl3ZmZ6KMiIiYWCvPLLIbtYAWiIDy6NU2jiT1wLlJ4G4jyV3DhIQ==", + "version": "0.6.0", + "resolved": "https://github.com/woocommerce/woocommerce-blocks/files/12309320/wordpress-e2e-test-utils-playwright-0.6.0.tgz", + "integrity": "sha512-5lo8iRuRfUOAPMRsQteSkKyMoP1IHHu6GKye8Df+YEPz1LWXcS6YRFGI3HrONBpcaHLrJC7LVav4UdB/sBPqUw==", "dev": true, "requires": { "@wordpress/api-fetch": "file:../api-fetch", @@ -66164,6 +66502,52 @@ "integrity": "sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==", "dev": true }, + "babel-jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", + "integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==", + "dev": true, + "requires": { + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^27.5.1", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "dependencies": { + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + } + } + }, + "babel-plugin-jest-hoist": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz", + "integrity": "sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==", + "dev": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.0.0", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz", + "integrity": "sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^27.5.1", + "babel-preset-current-node-syntax": "^1.0.0" + } + }, "ci-info": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.2.tgz", @@ -67045,6 +67429,52 @@ "babel-jest": "^27.4.5", "enzyme": "^3.11.0", "enzyme-to-json": "^3.4.4" + }, + "dependencies": { + "babel-jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", + "integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==", + "dev": true, + "requires": { + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^27.5.1", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + } + }, + "babel-plugin-jest-hoist": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz", + "integrity": "sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==", + "dev": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.0.0", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz", + "integrity": "sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^27.5.1", + "babel-preset-current-node-syntax": "^1.0.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + } } }, "@wordpress/jest-puppeteer-axe": { @@ -67747,6 +68177,52 @@ "integrity": "sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==", "dev": true }, + "babel-jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", + "integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==", + "dev": true, + "requires": { + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^27.5.1", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "dependencies": { + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + } + } + }, + "babel-plugin-jest-hoist": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz", + "integrity": "sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==", + "dev": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.0.0", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz", + "integrity": "sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^27.5.1", + "babel-preset-current-node-syntax": "^1.0.0" + } + }, "ci-info": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz", @@ -69777,26 +70253,133 @@ } }, "babel-jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", - "integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.6.2.tgz", + "integrity": "sha512-BYCzImLos6J3BH/+HvUCHG1dTf2MzmAB4jaVxHV+29RZLjR29XuYTmsf2sdDwkrb+FczkGo3kOhE7ga6sI0P4A==", "dev": true, "requires": { - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", + "@jest/transform": "^29.6.2", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^27.5.1", + "babel-preset-jest": "^29.5.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" }, "dependencies": { + "@jest/transform": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.6.2.tgz", + "integrity": "sha512-ZqCqEISr58Ce3U+buNFJYUktLJZOggfyvR+bZMaiV1e8B1SIvJbwZMrYz3gx/KAPn9EXmOmN+uB08yLCjWkQQg==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.1", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.6.2", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.6.2", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + } + }, + "@jest/types": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.1.tgz", + "integrity": "sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + } + }, + "@types/yargs": { + "version": "17.0.24", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", + "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true + }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "jest-haste-map": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.6.2.tgz", + "integrity": "sha512-+51XleTDAAysvU8rT6AnS1ZJ+WHVNqhj1k6nTvN2PYP+HjU3kqlaKQ1Lnw3NYW3bm2r8vq82X0Z1nDDHZMzHVA==", + "dev": true, + "requires": { + "@jest/types": "^29.6.1", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.6.2", + "jest-worker": "^29.6.2", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + } + }, + "jest-regex-util": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", + "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "dev": true + }, + "jest-util": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.2.tgz", + "integrity": "sha512-3eX1qb6L88lJNCFlEADKOkjpXJQyZRiavX1INZ4tRnrBVr2COd3RgcTLyUiEXMNBlDU/cgYq6taUS0fExrWW4w==", + "dev": true, + "requires": { + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + } + }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true + }, + "write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + } } } }, @@ -69976,14 +70559,14 @@ } }, "babel-plugin-jest-hoist": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz", - "integrity": "sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==", + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", + "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", "dev": true, "requires": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", + "@types/babel__core": "^7.1.14", "@types/babel__traverse": "^7.0.6" } }, @@ -70186,12 +70769,12 @@ } }, "babel-preset-jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz", - "integrity": "sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==", + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", + "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", "dev": true, "requires": { - "babel-plugin-jest-hoist": "^27.5.1", + "babel-plugin-jest-hoist": "^29.5.0", "babel-preset-current-node-syntax": "^1.0.0" } }, @@ -73591,13 +74174,10 @@ "dev": true }, "deepsignal": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/deepsignal/-/deepsignal-1.1.1.tgz", - "integrity": "sha512-ZcRi5KogA7hQiJcnUDGrDno7OCwfyBdcP96yf4BHF3JsUc8SRt3w24RZX6L0Ejijnol8Jt5cV5X8h0dotixHHA==", - "requires": { - "@preact/signals": "^1.0.0", - "@preact/signals-core": "^1.0.0" - } + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/deepsignal/-/deepsignal-1.3.6.tgz", + "integrity": "sha512-yjd+vtiznL6YaMptOsKnEKkPr60OEApa+LRe+Qe6Ile/RfCOrELKk/YM3qVpXFZiyOI3Ng67GDEyjAlqVc697g==", + "requires": {} }, "default-browser-id": { "version": "1.0.4", @@ -79537,6 +80117,44 @@ "jest-runtime": "^27.5.1" } }, + "babel-jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", + "integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==", + "dev": true, + "requires": { + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^27.5.1", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + } + }, + "babel-plugin-jest-hoist": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz", + "integrity": "sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==", + "dev": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.0.0", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz", + "integrity": "sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^27.5.1", + "babel-preset-current-node-syntax": "^1.0.0" + } + }, "ci-info": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.1.tgz", @@ -80007,43 +80625,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true }, - "babel-jest": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz", - "integrity": "sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==", - "dev": true, - "requires": { - "@jest/transform": "^29.5.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.5.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - } - }, - "babel-plugin-jest-hoist": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", - "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", - "dev": true, - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-preset-jest": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", - "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^29.5.0", - "babel-preset-current-node-syntax": "^1.0.0" - } - }, "ci-info": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", @@ -81964,24 +82545,24 @@ } }, "jest-worker": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", - "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.6.2.tgz", + "integrity": "sha512-l3ccBOabTdkng8I/ORCkADz4eSMKejTYv1vB/Z83UiubqhC1oQ5Li6dWCyqOIvSifGjUBxuvxvlm6KGK2DtuAQ==", "dev": true, "requires": { "@types/node": "*", - "jest-util": "^29.5.0", + "jest-util": "^29.6.2", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, "dependencies": { "@jest/types": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz", - "integrity": "sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.1.tgz", + "integrity": "sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw==", "dev": true, "requires": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.0", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -82005,12 +82586,12 @@ "dev": true }, "jest-util": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", - "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==", + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.2.tgz", + "integrity": "sha512-3eX1qb6L88lJNCFlEADKOkjpXJQyZRiavX1INZ4tRnrBVr2COd3RgcTLyUiEXMNBlDU/cgYq6taUS0fExrWW4w==", "dev": true, "requires": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.1", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -87596,9 +88177,9 @@ "dev": true }, "preact": { - "version": "10.11.3", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", - "integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==" + "version": "10.16.0", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.16.0.tgz", + "integrity": "sha512-XTSj3dJ4roKIC93pald6rWuB2qQJO9gO2iLLyTe87MrjQN+HklueLsmskbywEWqCHlclgz3/M4YLL2iBr9UmMA==" }, "prelude-ls": { "version": "1.2.1", diff --git a/package.json b/package.json index 520f4003de3..3b3547fafb7 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@woocommerce/block-library", "title": "WooCommerce Blocks", "author": "Automattic", - "version": "10.9.0-dev", + "version": "11.0.0-dev", "description": "WooCommerce blocks for the Gutenberg editor.", "homepage": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/", "keywords": [ @@ -81,6 +81,8 @@ "test:debug": "ndb .", "test:e2e": "sh ./bin/check-env.sh && npx playwright test --config=tests/e2e/playwright.config.ts", "test:e2e:report": "sh ./bin/check-env.sh && npx playwright test --config=tests/e2e/playwright.config.ts --reporter=html", + "test:e2e:side-effects": "npm run test:e2e -- --config=tests/e2e/playwright.side-effects.config.ts", + "test:e2e:side-effects:report": "npm run test:e2e:report -- --config=tests/e2e/playwright.side-effects.config.ts", "test:e2e:jest": "npm run wp-env:config && cross-env JEST_PUPPETEER_CONFIG=tests/e2e-jest/config/jest-puppeteer.config.js NODE_CONFIG_DIR=tests/e2e-jest/config wp-scripts test-e2e --config tests/e2e-jest/config/jest.config.js", "test:e2e:jest:dev": "npm run wp-env:config && cross-env JEST_PUPPETEER_CONFIG=tests/e2e-jest/config/jest-puppeteer.config-dev.js NODE_CONFIG_DIR=tests/e2e-jest/config wp-scripts test-e2e --config tests/e2e-jest/config/jest.config.js", "test:e2e:jest:dev-watch": "npm run wp-env:config && cross-env JEST_PUPPETEER_CONFIG=tests/e2e-jest/config/jest-puppeteer.config-dev.js NODE_CONFIG_DIR=tests/e2e-jest/config wp-scripts test-e2e --config tests/e2e-jest/config/jest.config.js --watch", @@ -164,7 +166,7 @@ "@wordpress/dependency-extraction-webpack-plugin": "3.2.1", "@wordpress/dom": "3.27.0", "@wordpress/e2e-test-utils": "10.1.0", - "@wordpress/e2e-test-utils-playwright": "https://github.com/woocommerce/woocommerce-blocks/files/11286971/wordpress-e2e-test-utils-playwright-0.0.0.tgz", + "@wordpress/e2e-test-utils-playwright": "0.6.0", "@wordpress/e2e-tests": "^4.6.0", "@wordpress/element": "4.20.0", "@wordpress/env": "7.0.0", @@ -175,6 +177,7 @@ "@wordpress/scripts": "24.6.0", "autoprefixer": "10.4.14", "axios": "0.27.2", + "babel-jest": "^29.6.2", "babel-plugin-explicit-exports-references": "^1.0.2", "babel-plugin-react-docgen": "4.2.1", "babel-plugin-transform-async-generator-functions": "6.24.1", @@ -267,7 +270,7 @@ "compare-versions": "4.1.3", "config": "3.3.7", "dataloader": "2.1.0", - "deepsignal": "^1.1.0", + "deepsignal": "1.3.6", "dinero.js": "1.9.1", "dompurify": "^2.4.0", "downshift": "6.1.7", diff --git a/patterns/featured-products-5-cols.php b/patterns/featured-products-5-cols.php index e793722a0f9..ae9556245bf 100644 --- a/patterns/featured-products-5-cols.php +++ b/patterns/featured-products-5-cols.php @@ -12,7 +12,7 @@ - +
diff --git a/patterns/featured-products-fresh-and-tasty.php b/patterns/featured-products-fresh-and-tasty.php index 8abaa92df69..5c987183e5d 100644 --- a/patterns/featured-products-fresh-and-tasty.php +++ b/patterns/featured-products-fresh-and-tasty.php @@ -9,28 +9,30 @@

- +
- +
- -
+ +
<?php esc_attr_e( 'Placeholder image used to represent a product being showcased as Sweet organic Lemons.', 'woo-gutenberg-products-block' ); ?>
- -
- -
+ +
+ +

-
+ +
- -
- -

+ + +
+ +

@@ -39,26 +41,28 @@
- +
- -
+ +
<?php esc_attr_e( 'Placeholder image used to represent a product being showcased as Fresh Organic Tomatoes.', 'woo-gutenberg-products-block' ); ?>
- -
- -
+ +
+ +

-
+ +
- -
- -

+ + +
+ +

@@ -67,28 +71,28 @@
- +
- -
+ +
<?php esc_attr_e( 'Placeholder image used to represent a product being showcased as Fresh Lettuce Washed.', 'woo-gutenberg-products-block' ); ?>
- -
- -
+ +
+ +

- -
- -

+ +
+ +

@@ -97,28 +101,28 @@
- +
- -
+ +
<?php esc_attr_e( 'Placeholder image used to represent a product being showcased as Russet Organic Potatoes.', 'woo-gutenberg-products-block' ); ?>
- -
- -
+ +
+ +

- -
- -

+ +
+ +

diff --git a/patterns/footer-large-dark.php b/patterns/footer-large-dark.php index 78243738bbc..27780e60f98 100644 --- a/patterns/footer-large-dark.php +++ b/patterns/footer-large-dark.php @@ -7,10 +7,8 @@ */ ?> - -