diff --git a/.syncpackrc b/.syncpackrc index 800b3580f29..14b98ade137 100644 --- a/.syncpackrc +++ b/.syncpackrc @@ -12,7 +12,7 @@ "dependencies": [ "pnpm" ], - "pinVersion": "^9.1.0", + "pinVersion": "9.1.3", "packages": [ "**" ] diff --git a/docs/.markdownlint.json b/docs/.markdownlint.json index 6728d0fd76a..3297523f014 100644 --- a/docs/.markdownlint.json +++ b/docs/.markdownlint.json @@ -3,7 +3,7 @@ "MD003": { "style": "atx" }, "MD007": { "indent": 4 }, "MD013": { "line_length": 9999 }, - "MD024": { "allow_different_nesting": true }, + "MD024": { "siblings_only": true }, "MD033": { "allowed_elements": ["video"] }, "MD035": false, "MD041": false, diff --git a/docs/docs-manifest.json b/docs/docs-manifest.json index 00c65ffeb77..9537ea57ba4 100644 --- a/docs/docs-manifest.json +++ b/docs/docs-manifest.json @@ -761,7 +761,7 @@ "menu_title": "Data storage", "tags": "reference", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/extension-development/data-storage.md", - "hash": "ea827670b3a888f69cb50bc78dc10827c802abb135a5846f31cfa55e882f7faa", + "hash": "1ea2fabf927f9ced2fee89f930e9195c7df4d98ceb3d847436337376578802a3", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/extension-development/data-storage.md", "id": "b3e0b17ca74596e858c26887c1e4c8ee6c8f6102" }, @@ -1059,7 +1059,7 @@ "categories": [] }, { - "content": "\nDiscover how to customize the WooCommerce product editor, from extending product data to adding unique functionalities.\n\nThis handbook is a guide for extension developers looking to add support for the new product editor in their extensions. The product editor uses [Gutenberg's Block Editor](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-editor), which is going to help WooCommerce evolve alongside the WordPress ecosystem.", + "content": "\nDiscover how to customize the WooCommerce product editor, from extending product data to adding unique functionalities.\n\nThis handbook is a guide for extension developers looking to add support for the new product editor in their extensions. The product editor uses [Gutenberg's Block Editor](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-editor), which is going to help WooCommerce evolve alongside the WordPress ecosystem.\n", "category_slug": "product-editor", "category_title": "Product Editor", "posts": [ @@ -1378,7 +1378,7 @@ { "post_title": "Template structure & Overriding templates via a theme", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/theme-development/template-structure.md", - "hash": "c0e346e7e21682bd645998f0607efe18f9f63601f53f4a53ab43248eefcab2d1", + "hash": "ff781eff7998ea93723f644bddd4f6da6f73c635bcfc3cd46950f03a8b83b26c", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/theme-development/template-structure.md", "id": "34bfebec9fc45e680976814928a7b8a1778af14e" }, @@ -1731,5 +1731,5 @@ "categories": [] } ], - "hash": "95e835afe855d5a898bfc31337d2a42b582794d5e1aaacb9b4a98cfc97748e05" + "hash": "b6fab4eae1266824ee3e876c8fa5fd0342f59b4a0e5978f1460afc67d82e6d94" } \ No newline at end of file diff --git a/docs/extension-development/data-storage.md b/docs/extension-development/data-storage.md index e6c05919192..8eb1350fa0b 100644 --- a/docs/extension-development/data-storage.md +++ b/docs/extension-development/data-storage.md @@ -7,21 +7,27 @@ tags: reference When developing for WordPress and WooCommerce, it's important to consider the nature and permanence of your data. This will help you decide the best way to store it. Here's a quick primer: ## Transients + If the data may not always be present (i.e., it expires), use a [transient](https://developer.wordpress.org/apis/handbook/transients/). Transients are a simple and standardized way of storing cached data in the database temporarily by giving it a custom name and a timeframe after which it will expire and be deleted. ## WP Cache + If the data is persistent but not always present, consider using the [WP Cache](https://developer.wordpress.org/reference/classes/wp_object_cache/). The WP Cache functions allow you to cache data that is computationally expensive to regenerate, such as complex query results. ## wp_options Table + If the data is persistent and always present, consider the [wp_options table](https://developer.wordpress.org/apis/handbook/options/). The Options API is a simple and standardized way of storing data in the wp_options table in the WordPress database. ## Post Types + If the data type is an entity with n units, consider a [post type](https://developer.wordpress.org/post_type/). Post types are "types" of content that are stored in the same way, but are easy to distinguish in the code and UI. ## Taxonomies + If the data is a means of sorting/categorizing an entity, consider a [taxonomy](https://developer.wordpress.org/taxonomy/). Taxonomies are a way of grouping things together. ## Logging + Logs should be written to a file using the [WC_Logger](https://woocommerce.com/wc-apidocs/class-WC_Logger.html) class. This is a simple and standardized way of recording events and errors for debugging purposes. Remember, the best method of data storage depends on the nature of the data and how it will be used in your application. diff --git a/docs/product-editor-development/README.md b/docs/product-editor-development/README.md index 1d717ad5322..db9e8f5d4bd 100644 --- a/docs/product-editor-development/README.md +++ b/docs/product-editor-development/README.md @@ -6,4 +6,4 @@ post_title: Product Editor Discover how to customize the WooCommerce product editor, from extending product data to adding unique functionalities. -This handbook is a guide for extension developers looking to add support for the new product editor in their extensions. The product editor uses [Gutenberg's Block Editor](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-editor), which is going to help WooCommerce evolve alongside the WordPress ecosystem. \ No newline at end of file +This handbook is a guide for extension developers looking to add support for the new product editor in their extensions. The product editor uses [Gutenberg's Block Editor](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-editor), which is going to help WooCommerce evolve alongside the WordPress ecosystem. diff --git a/package.json b/package.json index ed0db1d6f99..e01a7c1facb 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "packageManager": "pnpm@9.1.3", "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "private": true, "repository": { diff --git a/packages/js/admin-e2e-tests/changelog/50828-dev-constrain-pnpm-version b/packages/js/admin-e2e-tests/changelog/50828-dev-constrain-pnpm-version new file mode 100644 index 00000000000..fdead95ceb5 --- /dev/null +++ b/packages/js/admin-e2e-tests/changelog/50828-dev-constrain-pnpm-version @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Fix pnpm version to 9.1.3 to avoid dependency installation issues. \ No newline at end of file diff --git a/packages/js/admin-e2e-tests/package.json b/packages/js/admin-e2e-tests/package.json index 933b9d413a0..8113a5dbb03 100644 --- a/packages/js/admin-e2e-tests/package.json +++ b/packages/js/admin-e2e-tests/package.json @@ -6,7 +6,7 @@ "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/admin-e2e-tests/README.md", "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "repository": { "type": "git", diff --git a/packages/js/api-core-tests/package.json b/packages/js/api-core-tests/package.json index a5c1a442eb4..a0667d48bfd 100644 --- a/packages/js/api-core-tests/package.json +++ b/packages/js/api-core-tests/package.json @@ -5,7 +5,7 @@ "main": "index.js", "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "scripts": { "e2e": "jest", diff --git a/packages/js/api/changelog/50828-dev-constrain-pnpm-version b/packages/js/api/changelog/50828-dev-constrain-pnpm-version new file mode 100644 index 00000000000..fdead95ceb5 --- /dev/null +++ b/packages/js/api/changelog/50828-dev-constrain-pnpm-version @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Fix pnpm version to 9.1.3 to avoid dependency installation issues. \ No newline at end of file diff --git a/packages/js/api/package.json b/packages/js/api/package.json index f428f5581ab..3cf9a7d739d 100644 --- a/packages/js/api/package.json +++ b/packages/js/api/package.json @@ -6,7 +6,7 @@ "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/api/README.md", "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "repository": { "type": "git", diff --git a/packages/js/components/changelog/50828-dev-constrain-pnpm-version b/packages/js/components/changelog/50828-dev-constrain-pnpm-version new file mode 100644 index 00000000000..fdead95ceb5 --- /dev/null +++ b/packages/js/components/changelog/50828-dev-constrain-pnpm-version @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Fix pnpm version to 9.1.3 to avoid dependency installation issues. \ No newline at end of file diff --git a/packages/js/components/package.json b/packages/js/components/package.json index fbb42628467..49781d18732 100644 --- a/packages/js/components/package.json +++ b/packages/js/components/package.json @@ -6,7 +6,7 @@ "license": "GPL-3.0-or-later", "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "keywords": [ "wordpress", diff --git a/packages/js/create-product-editor-block/changelog/50828-dev-constrain-pnpm-version b/packages/js/create-product-editor-block/changelog/50828-dev-constrain-pnpm-version new file mode 100644 index 00000000000..fdead95ceb5 --- /dev/null +++ b/packages/js/create-product-editor-block/changelog/50828-dev-constrain-pnpm-version @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Fix pnpm version to 9.1.3 to avoid dependency installation issues. \ No newline at end of file diff --git a/packages/js/create-product-editor-block/package.json b/packages/js/create-product-editor-block/package.json index c21112cd09d..0e69b805f17 100644 --- a/packages/js/create-product-editor-block/package.json +++ b/packages/js/create-product-editor-block/package.json @@ -7,7 +7,7 @@ "main": "index.js", "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "scripts": { "changelog": "composer install && composer exec -- changelogger" diff --git a/packages/js/create-woo-extension/changelog/50828-dev-constrain-pnpm-version b/packages/js/create-woo-extension/changelog/50828-dev-constrain-pnpm-version new file mode 100644 index 00000000000..fdead95ceb5 --- /dev/null +++ b/packages/js/create-woo-extension/changelog/50828-dev-constrain-pnpm-version @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Fix pnpm version to 9.1.3 to avoid dependency installation issues. \ No newline at end of file diff --git a/packages/js/create-woo-extension/package.json b/packages/js/create-woo-extension/package.json index d7f3be1588f..6f5a61495b7 100644 --- a/packages/js/create-woo-extension/package.json +++ b/packages/js/create-woo-extension/package.json @@ -5,7 +5,7 @@ "main": "index.js", "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "repository": { "type": "git", diff --git a/packages/js/csv-export/changelog/50828-dev-constrain-pnpm-version b/packages/js/csv-export/changelog/50828-dev-constrain-pnpm-version new file mode 100644 index 00000000000..fdead95ceb5 --- /dev/null +++ b/packages/js/csv-export/changelog/50828-dev-constrain-pnpm-version @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Fix pnpm version to 9.1.3 to avoid dependency installation issues. \ No newline at end of file diff --git a/packages/js/csv-export/package.json b/packages/js/csv-export/package.json index 772d22e3c16..11a576f414f 100644 --- a/packages/js/csv-export/package.json +++ b/packages/js/csv-export/package.json @@ -6,7 +6,7 @@ "license": "GPL-3.0-or-later", "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "keywords": [ "wordpress", diff --git a/packages/js/currency/changelog/50828-dev-constrain-pnpm-version b/packages/js/currency/changelog/50828-dev-constrain-pnpm-version new file mode 100644 index 00000000000..fdead95ceb5 --- /dev/null +++ b/packages/js/currency/changelog/50828-dev-constrain-pnpm-version @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Fix pnpm version to 9.1.3 to avoid dependency installation issues. \ No newline at end of file diff --git a/packages/js/currency/package.json b/packages/js/currency/package.json index 8e36f833963..a8b5ee2d4ce 100644 --- a/packages/js/currency/package.json +++ b/packages/js/currency/package.json @@ -11,7 +11,7 @@ ], "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/currency/README.md", "repository": { diff --git a/packages/js/customer-effort-score/changelog/50828-dev-constrain-pnpm-version b/packages/js/customer-effort-score/changelog/50828-dev-constrain-pnpm-version new file mode 100644 index 00000000000..fdead95ceb5 --- /dev/null +++ b/packages/js/customer-effort-score/changelog/50828-dev-constrain-pnpm-version @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Fix pnpm version to 9.1.3 to avoid dependency installation issues. \ No newline at end of file diff --git a/packages/js/customer-effort-score/package.json b/packages/js/customer-effort-score/package.json index 110b88b358e..8c78924af57 100644 --- a/packages/js/customer-effort-score/package.json +++ b/packages/js/customer-effort-score/package.json @@ -10,7 +10,7 @@ ], "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/customer-effort-score/README.md", "repository": { diff --git a/packages/js/data/changelog/50828-dev-constrain-pnpm-version b/packages/js/data/changelog/50828-dev-constrain-pnpm-version new file mode 100644 index 00000000000..fdead95ceb5 --- /dev/null +++ b/packages/js/data/changelog/50828-dev-constrain-pnpm-version @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Fix pnpm version to 9.1.3 to avoid dependency installation issues. \ No newline at end of file diff --git a/packages/js/data/package.json b/packages/js/data/package.json index c52f4e22698..178022e8a2b 100644 --- a/packages/js/data/package.json +++ b/packages/js/data/package.json @@ -11,7 +11,7 @@ ], "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/data/README.md", "repository": { diff --git a/packages/js/date/changelog/50828-dev-constrain-pnpm-version b/packages/js/date/changelog/50828-dev-constrain-pnpm-version new file mode 100644 index 00000000000..fdead95ceb5 --- /dev/null +++ b/packages/js/date/changelog/50828-dev-constrain-pnpm-version @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Fix pnpm version to 9.1.3 to avoid dependency installation issues. \ No newline at end of file diff --git a/packages/js/date/package.json b/packages/js/date/package.json index 17e3f6152c8..d7f2fef1241 100644 --- a/packages/js/date/package.json +++ b/packages/js/date/package.json @@ -11,7 +11,7 @@ ], "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/date/README.md", "repository": { diff --git a/packages/js/dependency-extraction-webpack-plugin/changelog/50828-dev-constrain-pnpm-version b/packages/js/dependency-extraction-webpack-plugin/changelog/50828-dev-constrain-pnpm-version new file mode 100644 index 00000000000..fdead95ceb5 --- /dev/null +++ b/packages/js/dependency-extraction-webpack-plugin/changelog/50828-dev-constrain-pnpm-version @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Fix pnpm version to 9.1.3 to avoid dependency installation issues. \ No newline at end of file diff --git a/packages/js/dependency-extraction-webpack-plugin/package.json b/packages/js/dependency-extraction-webpack-plugin/package.json index 18eb2cca63e..869625dd58b 100644 --- a/packages/js/dependency-extraction-webpack-plugin/package.json +++ b/packages/js/dependency-extraction-webpack-plugin/package.json @@ -10,7 +10,7 @@ ], "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/dependency-extraction-webpack-plugin/README.md", "repository": { diff --git a/packages/js/e2e-core-tests/package.json b/packages/js/e2e-core-tests/package.json index 7aa1cb71ad0..3f512664f5b 100644 --- a/packages/js/e2e-core-tests/package.json +++ b/packages/js/e2e-core-tests/package.json @@ -10,7 +10,7 @@ "license": "GPL-3.0+", "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "main": "build/index.js", "module": "build-module/index.js", diff --git a/packages/js/e2e-environment/package.json b/packages/js/e2e-environment/package.json index f73aff3163c..f5f0afa6411 100644 --- a/packages/js/e2e-environment/package.json +++ b/packages/js/e2e-environment/package.json @@ -12,7 +12,7 @@ ], "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/e2e-environment/README.md", "bugs": { diff --git a/packages/js/e2e-utils/package.json b/packages/js/e2e-utils/package.json index d133e6c9a8f..1e47b320022 100644 --- a/packages/js/e2e-utils/package.json +++ b/packages/js/e2e-utils/package.json @@ -10,7 +10,7 @@ "license": "GPL-3.0+", "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "main": "build/index.js", "module": "build-module/index.js", diff --git a/packages/js/eslint-plugin/changelog/50828-dev-constrain-pnpm-version b/packages/js/eslint-plugin/changelog/50828-dev-constrain-pnpm-version new file mode 100644 index 00000000000..fdead95ceb5 --- /dev/null +++ b/packages/js/eslint-plugin/changelog/50828-dev-constrain-pnpm-version @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Fix pnpm version to 9.1.3 to avoid dependency installation issues. \ No newline at end of file diff --git a/packages/js/eslint-plugin/package.json b/packages/js/eslint-plugin/package.json index 7913b5a2b3e..4e5407484dc 100644 --- a/packages/js/eslint-plugin/package.json +++ b/packages/js/eslint-plugin/package.json @@ -6,7 +6,7 @@ "license": "GPL-2.0-or-later", "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "keywords": [ "wordpress", diff --git a/packages/js/experimental/changelog/50828-dev-constrain-pnpm-version b/packages/js/experimental/changelog/50828-dev-constrain-pnpm-version new file mode 100644 index 00000000000..fdead95ceb5 --- /dev/null +++ b/packages/js/experimental/changelog/50828-dev-constrain-pnpm-version @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Fix pnpm version to 9.1.3 to avoid dependency installation issues. \ No newline at end of file diff --git a/packages/js/experimental/package.json b/packages/js/experimental/package.json index de525dbc624..3a2016f62ce 100644 --- a/packages/js/experimental/package.json +++ b/packages/js/experimental/package.json @@ -6,7 +6,7 @@ "license": "GPL-3.0-or-later", "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "keywords": [ "wordpress", diff --git a/packages/js/explat/changelog/50828-dev-constrain-pnpm-version b/packages/js/explat/changelog/50828-dev-constrain-pnpm-version new file mode 100644 index 00000000000..fdead95ceb5 --- /dev/null +++ b/packages/js/explat/changelog/50828-dev-constrain-pnpm-version @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Fix pnpm version to 9.1.3 to avoid dependency installation issues. \ No newline at end of file diff --git a/packages/js/explat/package.json b/packages/js/explat/package.json index 376e4893ae4..377b1f25e98 100644 --- a/packages/js/explat/package.json +++ b/packages/js/explat/package.json @@ -6,7 +6,7 @@ "license": "GPL-2.0-or-later", "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "keywords": [ "wordpress", diff --git a/packages/js/expression-evaluation/changelog/50828-dev-constrain-pnpm-version b/packages/js/expression-evaluation/changelog/50828-dev-constrain-pnpm-version new file mode 100644 index 00000000000..fdead95ceb5 --- /dev/null +++ b/packages/js/expression-evaluation/changelog/50828-dev-constrain-pnpm-version @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Fix pnpm version to 9.1.3 to avoid dependency installation issues. \ No newline at end of file diff --git a/packages/js/expression-evaluation/package.json b/packages/js/expression-evaluation/package.json index dc8316bd48c..0361729ee1c 100644 --- a/packages/js/expression-evaluation/package.json +++ b/packages/js/expression-evaluation/package.json @@ -12,7 +12,7 @@ ], "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/expression-evaluation/README.md", "repository": { diff --git a/packages/js/extend-cart-checkout-block/changelog/50828-dev-constrain-pnpm-version b/packages/js/extend-cart-checkout-block/changelog/50828-dev-constrain-pnpm-version new file mode 100644 index 00000000000..fdead95ceb5 --- /dev/null +++ b/packages/js/extend-cart-checkout-block/changelog/50828-dev-constrain-pnpm-version @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Fix pnpm version to 9.1.3 to avoid dependency installation issues. \ No newline at end of file diff --git a/packages/js/extend-cart-checkout-block/package.json b/packages/js/extend-cart-checkout-block/package.json index 5695910fd8c..0e032c37149 100644 --- a/packages/js/extend-cart-checkout-block/package.json +++ b/packages/js/extend-cart-checkout-block/package.json @@ -5,7 +5,7 @@ "main": "index.js", "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "repository": { "type": "git", diff --git a/packages/js/internal-e2e-builds/package.json b/packages/js/internal-e2e-builds/package.json index 49c4030cf54..ba7b5c89049 100644 --- a/packages/js/internal-e2e-builds/package.json +++ b/packages/js/internal-e2e-builds/package.json @@ -10,7 +10,7 @@ ], "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "bin": { "e2e-builds": "./build.js" diff --git a/packages/js/internal-js-tests/changelog/50828-dev-constrain-pnpm-version b/packages/js/internal-js-tests/changelog/50828-dev-constrain-pnpm-version new file mode 100644 index 00000000000..fdead95ceb5 --- /dev/null +++ b/packages/js/internal-js-tests/changelog/50828-dev-constrain-pnpm-version @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Fix pnpm version to 9.1.3 to avoid dependency installation issues. \ No newline at end of file diff --git a/packages/js/internal-js-tests/package.json b/packages/js/internal-js-tests/package.json index 2a112db89d5..4f1049b8572 100644 --- a/packages/js/internal-js-tests/package.json +++ b/packages/js/internal-js-tests/package.json @@ -6,7 +6,7 @@ "license": "GPL-2.0-or-later", "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/internal-js-tests/README.md", "repository": { diff --git a/packages/js/internal-style-build/package.json b/packages/js/internal-style-build/package.json index a777f36e231..9955a65abe9 100644 --- a/packages/js/internal-style-build/package.json +++ b/packages/js/internal-style-build/package.json @@ -6,7 +6,7 @@ "license": "GPL-2.0-or-later", "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "keywords": [ "wordpress", diff --git a/packages/js/navigation/changelog/50828-dev-constrain-pnpm-version b/packages/js/navigation/changelog/50828-dev-constrain-pnpm-version new file mode 100644 index 00000000000..fdead95ceb5 --- /dev/null +++ b/packages/js/navigation/changelog/50828-dev-constrain-pnpm-version @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Fix pnpm version to 9.1.3 to avoid dependency installation issues. \ No newline at end of file diff --git a/packages/js/navigation/package.json b/packages/js/navigation/package.json index 903b2f3ef26..56c29eacab8 100644 --- a/packages/js/navigation/package.json +++ b/packages/js/navigation/package.json @@ -6,7 +6,7 @@ "license": "GPL-3.0-or-later", "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "keywords": [ "wordpress", diff --git a/packages/js/notices/changelog/50828-dev-constrain-pnpm-version b/packages/js/notices/changelog/50828-dev-constrain-pnpm-version new file mode 100644 index 00000000000..fdead95ceb5 --- /dev/null +++ b/packages/js/notices/changelog/50828-dev-constrain-pnpm-version @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Fix pnpm version to 9.1.3 to avoid dependency installation issues. \ No newline at end of file diff --git a/packages/js/notices/package.json b/packages/js/notices/package.json index 0a649b7c7b3..b2c5ac704f8 100644 --- a/packages/js/notices/package.json +++ b/packages/js/notices/package.json @@ -6,7 +6,7 @@ "license": "GPL-2.0-or-later", "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "keywords": [ "wordpress", diff --git a/packages/js/number/changelog/50828-dev-constrain-pnpm-version b/packages/js/number/changelog/50828-dev-constrain-pnpm-version new file mode 100644 index 00000000000..fdead95ceb5 --- /dev/null +++ b/packages/js/number/changelog/50828-dev-constrain-pnpm-version @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Fix pnpm version to 9.1.3 to avoid dependency installation issues. \ No newline at end of file diff --git a/packages/js/number/package.json b/packages/js/number/package.json index eebf9a2dede..44a2332d8df 100644 --- a/packages/js/number/package.json +++ b/packages/js/number/package.json @@ -6,7 +6,7 @@ "license": "GPL-3.0-or-later", "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "keywords": [ "wordpress", diff --git a/packages/js/onboarding/changelog/50828-dev-constrain-pnpm-version b/packages/js/onboarding/changelog/50828-dev-constrain-pnpm-version new file mode 100644 index 00000000000..fdead95ceb5 --- /dev/null +++ b/packages/js/onboarding/changelog/50828-dev-constrain-pnpm-version @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Fix pnpm version to 9.1.3 to avoid dependency installation issues. \ No newline at end of file diff --git a/packages/js/onboarding/package.json b/packages/js/onboarding/package.json index 55825d51629..0521b2f2163 100644 --- a/packages/js/onboarding/package.json +++ b/packages/js/onboarding/package.json @@ -6,7 +6,7 @@ "license": "GPL-3.0-or-later", "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "keywords": [ "wordpress", diff --git a/packages/js/remote-logging/changelog/50828-dev-constrain-pnpm-version b/packages/js/remote-logging/changelog/50828-dev-constrain-pnpm-version new file mode 100644 index 00000000000..fdead95ceb5 --- /dev/null +++ b/packages/js/remote-logging/changelog/50828-dev-constrain-pnpm-version @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Fix pnpm version to 9.1.3 to avoid dependency installation issues. \ No newline at end of file diff --git a/packages/js/remote-logging/package.json b/packages/js/remote-logging/package.json index 8bdc1cb7ace..c96e6ca3f18 100644 --- a/packages/js/remote-logging/package.json +++ b/packages/js/remote-logging/package.json @@ -6,7 +6,7 @@ "license": "GPL-2.0-or-later", "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "keywords": [ "wordpress", diff --git a/packages/js/tracks/changelog/50828-dev-constrain-pnpm-version b/packages/js/tracks/changelog/50828-dev-constrain-pnpm-version new file mode 100644 index 00000000000..fdead95ceb5 --- /dev/null +++ b/packages/js/tracks/changelog/50828-dev-constrain-pnpm-version @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Fix pnpm version to 9.1.3 to avoid dependency installation issues. \ No newline at end of file diff --git a/packages/js/tracks/package.json b/packages/js/tracks/package.json index 82aaf9e44ee..50fee1aac21 100644 --- a/packages/js/tracks/package.json +++ b/packages/js/tracks/package.json @@ -6,7 +6,7 @@ "license": "GPL-3.0-or-later", "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "keywords": [ "wordpress", diff --git a/plugins/woo-ai/changelog/50828-dev-constrain-pnpm-version b/plugins/woo-ai/changelog/50828-dev-constrain-pnpm-version new file mode 100644 index 00000000000..fdead95ceb5 --- /dev/null +++ b/plugins/woo-ai/changelog/50828-dev-constrain-pnpm-version @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Fix pnpm version to 9.1.3 to avoid dependency installation issues. \ No newline at end of file diff --git a/plugins/woo-ai/package.json b/plugins/woo-ai/package.json index c511404c47b..3083d8d3cfe 100644 --- a/plugins/woo-ai/package.json +++ b/plugins/woo-ai/package.json @@ -94,7 +94,7 @@ }, "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "lint-staged": { "*.php": [ diff --git a/plugins/woocommerce-admin/client/customize-store/design-with-ai/services.ts b/plugins/woocommerce-admin/client/customize-store/design-with-ai/services.ts index 3e16f4683ac..b4008b58dde 100644 --- a/plugins/woocommerce-admin/client/customize-store/design-with-ai/services.ts +++ b/plugins/woocommerce-admin/client/customize-store/design-with-ai/services.ts @@ -220,7 +220,7 @@ const resetPatternsAndProducts = () => async () => { method: 'DELETE', } ), apiFetch( { - path: '/wc/private/ai/products', + path: '/wc-admin/ai/products', method: 'DELETE', } ), ] ); @@ -278,7 +278,7 @@ export const updateStorePatterns = async ( additional_errors?: unknown[]; } >( [ apiFetch( { - path: '/wc/private/ai/products', + path: '/wc-admin/ai/products', method: 'POST', data: { business_description: diff --git a/plugins/woocommerce-admin/package.json b/plugins/woocommerce-admin/package.json index 436ea164243..8e84eeb1cd5 100644 --- a/plugins/woocommerce-admin/package.json +++ b/plugins/woocommerce-admin/package.json @@ -242,7 +242,7 @@ }, "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "config": { "ci": { diff --git a/plugins/woocommerce-beta-tester/api/api.php b/plugins/woocommerce-beta-tester/api/api.php index 16c1ef5e7cc..65b1d151aa8 100644 --- a/plugins/woocommerce-beta-tester/api/api.php +++ b/plugins/woocommerce-beta-tester/api/api.php @@ -55,6 +55,7 @@ require 'tools/trigger-update-callbacks.php'; require 'tools/reset-cys.php'; require 'tools/set-block-template-logging-threshold.php'; require 'tools/set-coming-soon-mode.php'; +require 'tools/fake-woo-payments-gateway.php'; require 'tracks/class-tracks-debug-log.php'; require 'features/features.php'; require 'rest-api-filters/class-wca-test-helper-rest-api-filters.php'; diff --git a/plugins/woocommerce-beta-tester/api/tools/class-fake-wcpayments.php b/plugins/woocommerce-beta-tester/api/tools/class-fake-wcpayments.php new file mode 100644 index 00000000000..01fa658c3a4 --- /dev/null +++ b/plugins/woocommerce-beta-tester/api/tools/class-fake-wcpayments.php @@ -0,0 +1,62 @@ +id = 'woocommerce_payments'; + $this->has_fields = true; + $this->method_title = 'WooPayments'; + $this->method_description = $this->get_method_description(); + + $this->description = ''; + $this->supports = array( + 'products', + 'refunds', + ); + } + + /** + * Returns true if the gateway needs additional configuration, false if it's ready to use. + * + * @see WC_Payment_Gateway::needs_setup + * @return bool + */ + public function needs_setup() { + return false; + } + + /** + * Check if the payment gateway is connected. This method is also used by + * external plugins to check if a connection has been established. + */ + public function is_connected() { + return true; + } + + /** + * Get the connection URL. + * Called directly by WooCommerce Core. + * + * @return string Connection URL. + */ + public function get_connection_url() { + return ''; + } + + /** + * Checks if the gateway is enabled, and also if it's configured enough to accept payments from customers. + * + * Use parent method value alongside other business rules to make the decision. + * + * @return bool Whether the gateway is enabled and ready to accept payments. + */ + public function is_available() { + return true; + } +} diff --git a/plugins/woocommerce-beta-tester/api/tools/fake-woo-payments-gateway.php b/plugins/woocommerce-beta-tester/api/tools/fake-woo-payments-gateway.php new file mode 100644 index 00000000000..c1f5d9f00e7 --- /dev/null +++ b/plugins/woocommerce-beta-tester/api/tools/fake-woo-payments-gateway.php @@ -0,0 +1,73 @@ + 'GET', + ) +); + +register_woocommerce_admin_test_helper_rest_route( + '/tools/fake-wcpay-completion/v1', + 'tools_set_fake_wcpay_completion', + array( + 'methods' => 'POST', + 'args' => array( + 'enabled' => array( + 'type' => 'enum', + 'enum' => array( 'yes', 'no' ), + 'required' => true, + 'description' => 'Whether to enable or disable fake WooPayments completion', + ), + ), + ) +); + +/** + * Get the current status of fake WooPayments completion. + */ +function tools_get_fake_wcpay_completion_status() { + return new WP_REST_Response( array( 'enabled' => get_option( 'wc_beta_tester_fake_wcpay_completion', 'no' ) ), 200 ); +} + +/** + * A tool to enable/disable fake WooPayments completion. + * + * @param WP_REST_Request $request Request object. + */ +function tools_set_fake_wcpay_completion( $request ) { + $enabled = $request->get_param( 'enabled' ); + update_option( 'wc_beta_tester_fake_wcpay_completion', $enabled ); + + return new WP_REST_Response( array( 'enabled' => $enabled ), 200 ); +} + + +if ( + 'yes' === get_option( 'wc_beta_tester_fake_wcpay_completion', 'no' ) && + class_exists( 'WC_Payment_Gateway_WCPay' ) +) { + add_filter( 'woocommerce_payment_gateways', 'tools_fake_wcpay' ); + add_filter( 'woocommerce_available_payment_gateways', 'tools_fake_wcpay' ); + + require_once dirname( __FILE__ ) . '/class-fake-wcpayments.php'; + + /** + * Fake WooPayments completion. + * + * @param array $gateways List of available payment gateways. + */ + function tools_fake_wcpay( $gateways ) { + $gateways['woocommerce_payments'] = new Fake_WCPayments(); + return $gateways; + } +} diff --git a/plugins/woocommerce-beta-tester/changelog/50828-dev-constrain-pnpm-version b/plugins/woocommerce-beta-tester/changelog/50828-dev-constrain-pnpm-version new file mode 100644 index 00000000000..fdead95ceb5 --- /dev/null +++ b/plugins/woocommerce-beta-tester/changelog/50828-dev-constrain-pnpm-version @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Fix pnpm version to 9.1.3 to avoid dependency installation issues. \ No newline at end of file diff --git a/plugins/woocommerce-beta-tester/changelog/add-beta-tester-tool-fake-wcpay b/plugins/woocommerce-beta-tester/changelog/add-beta-tester-tool-fake-wcpay new file mode 100644 index 00000000000..35549787397 --- /dev/null +++ b/plugins/woocommerce-beta-tester/changelog/add-beta-tester-tool-fake-wcpay @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add fake WooPayments completion tool diff --git a/plugins/woocommerce-beta-tester/package.json b/plugins/woocommerce-beta-tester/package.json index 2a8949e630c..503f1c40de8 100644 --- a/plugins/woocommerce-beta-tester/package.json +++ b/plugins/woocommerce-beta-tester/package.json @@ -91,7 +91,7 @@ }, "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "lint-staged": { "*.php": [ diff --git a/plugins/woocommerce-beta-tester/src/tools/commands/fake-woo-payments.js b/plugins/woocommerce-beta-tester/src/tools/commands/fake-woo-payments.js new file mode 100644 index 00000000000..ef41cf76fb6 --- /dev/null +++ b/plugins/woocommerce-beta-tester/src/tools/commands/fake-woo-payments.js @@ -0,0 +1,31 @@ +/** + * External dependencies + */ +import { useSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { STORE_KEY } from '../data/constants'; + +export const FAKE_WOO_PAYMENTS_ACTION_NAME = 'fakeWooPayments'; + +export const FakeWooPayments = () => { + const isEnabled = useSelect( ( select ) => + select( STORE_KEY ).getIsFakeWooPaymentsEnabled() + ); + const getDescription = () => { + switch ( isEnabled ) { + case 'yes': + return 'Enabled 🟢'; + case 'no': + return 'Disabled 🔴'; + case 'error': + return 'Error 🙁'; + default: + return 'Loading ...'; + } + }; + + return
{ getDescription() }
; +}; diff --git a/plugins/woocommerce-beta-tester/src/tools/commands/index.js b/plugins/woocommerce-beta-tester/src/tools/commands/index.js index 6283f0e35a8..66ba187eefa 100644 --- a/plugins/woocommerce-beta-tester/src/tools/commands/index.js +++ b/plugins/woocommerce-beta-tester/src/tools/commands/index.js @@ -15,6 +15,10 @@ import { SetComingSoonMode, UPDATE_COMING_SOON_MODE_ACTION_NAME, } from './set-coming-soon-mode'; +import { + FakeWooPayments, + FAKE_WOO_PAYMENTS_ACTION_NAME, +} from './fake-woo-payments'; import { UPDATE_WCCOM_REQUEST_ERRORS_MODE, @@ -97,4 +101,9 @@ export default [ description: , action: UPDATE_WCCOM_REQUEST_ERRORS_MODE, }, + { + command: 'Toggle Fake WooPayments Completion Status', + description: , + action: FAKE_WOO_PAYMENTS_ACTION_NAME, + }, ]; diff --git a/plugins/woocommerce-beta-tester/src/tools/data/actions.js b/plugins/woocommerce-beta-tester/src/tools/data/actions.js index e4455482694..e2cc1e67cb4 100644 --- a/plugins/woocommerce-beta-tester/src/tools/data/actions.js +++ b/plugins/woocommerce-beta-tester/src/tools/data/actions.js @@ -282,3 +282,28 @@ export function* updateWccomRequestErrorsMode( params ) { } ); } ); } + +export function* fakeWooPayments( params ) { + yield runCommand( 'Toggle Fake WooPayments Completion', function* () { + const newStatus = params.enabled === 'yes' ? 'no' : 'yes'; + + yield apiFetch( { + path: API_NAMESPACE + '/tools/fake-wcpay-completion/v1', + method: 'POST', + data: { + enabled: newStatus, + }, + } ); + + yield updateCommandParams( 'fakeWooPayments', { + enabled: newStatus, + } ); + + yield updateMessage( + 'Toggle Fake WooPayments Completion', + `Fake WooPayments completion ${ + newStatus === 'yes' ? 'disabled' : 'enabled' + }` + ); + } ); +} diff --git a/plugins/woocommerce-beta-tester/src/tools/data/reducer.js b/plugins/woocommerce-beta-tester/src/tools/data/reducer.js index 2545784a6db..481561117d8 100644 --- a/plugins/woocommerce-beta-tester/src/tools/data/reducer.js +++ b/plugins/woocommerce-beta-tester/src/tools/data/reducer.js @@ -14,6 +14,7 @@ const DEFAULT_STATE = { updateBlockTemplateLoggingThreshold: {}, runSelectedUpdateCallbacks: {}, updateWccomRequestErrorsMode: {}, + fakeWooPayments: {}, }, status: '', dbUpdateVersions: [], diff --git a/plugins/woocommerce-beta-tester/src/tools/data/resolvers.js b/plugins/woocommerce-beta-tester/src/tools/data/resolvers.js index 81065ce3170..0e839b27760 100644 --- a/plugins/woocommerce-beta-tester/src/tools/data/resolvers.js +++ b/plugins/woocommerce-beta-tester/src/tools/data/resolvers.js @@ -18,6 +18,7 @@ import { UPDATE_BLOCK_TEMPLATE_LOGGING_THRESHOLD_ACTION_NAME } from '../commands import { UPDATE_COMING_SOON_MODE_ACTION_NAME } from '../commands/set-coming-soon-mode'; import { TRIGGER_UPDATE_CALLBACKS_ACTION_NAME } from '../commands/trigger-update-callbacks'; import { UPDATE_WCCOM_REQUEST_ERRORS_MODE } from '../commands/set-wccom-request-errors'; +import { FAKE_WOO_PAYMENTS_ACTION_NAME } from '../commands/fake-woo-payments'; export function* getCronJobs() { const path = `${ API_NAMESPACE }/tools/get-cron-list/v1`; @@ -135,3 +136,17 @@ export function* getWccomRequestErrorsMode() { throw new Error( error ); } } + +export function* getIsFakeWooPaymentsEnabled() { + try { + const response = yield apiFetch( { + path: API_NAMESPACE + '/tools/fake-wcpay-completion/v1', + method: 'GET', + } ); + yield updateCommandParams( FAKE_WOO_PAYMENTS_ACTION_NAME, { + enabled: response.enabled || 'no', + } ); + } catch ( error ) { + throw new Error( error ); + } +} diff --git a/plugins/woocommerce-beta-tester/src/tools/data/selectors.js b/plugins/woocommerce-beta-tester/src/tools/data/selectors.js index b53025ff4b1..bca03b9bbf9 100644 --- a/plugins/woocommerce-beta-tester/src/tools/data/selectors.js +++ b/plugins/woocommerce-beta-tester/src/tools/data/selectors.js @@ -41,3 +41,7 @@ export function getComingSoonMode( state ) { export function getWccomRequestErrorsMode( state ) { return state.params.updateWccomRequestErrorsMode.mode; } + +export function getIsFakeWooPaymentsEnabled( state ) { + return state.params.fakeWooPayments.enabled; +} diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/image/image-size-settings.tsx b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/image/image-size-settings.tsx index fc4839332fc..5b9f3620d58 100644 --- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/image/image-size-settings.tsx +++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/image/image-size-settings.tsx @@ -43,6 +43,33 @@ const scaleHelp: Record< string, string > = { ), }; +const sizeUnits: { value: string; label: string }[] = [ + { + value: 'px', + label: 'px', + }, + { + value: 'em', + label: 'em', + }, + { + value: 'rem', + label: 'rem', + }, + { + value: '%', + label: '%', + }, + { + value: 'vw', + label: 'vw', + }, + { + value: 'vh', + label: 'vh', + }, +]; + export const ImageSizeSettings = ( { scale, width, @@ -60,12 +87,7 @@ export const ImageSizeSettings = ( { setAttributes( { height: value } ); } } value={ height } - units={ [ - { - value: 'px', - label: 'px', - }, - ] } + units={ sizeUnits } /> { height && ( ( { + pageObject: async ( { page, admin, editor }, use ) => { + const pageObject = new ProductCollectionPage( { + page, + admin, + editor, + } ); + await use( pageObject ); + }, +} ); + +test.describe( 'Product Collection', () => { + test.describe( 'Inspector Controls', () => { + test( 'Reflects the correct number of columns according to sidebar settings', async ( { + pageObject, + } ) => { + await pageObject.createNewPostAndInsertBlock(); + + await pageObject.setNumberOfColumns( 2 ); + await expect( pageObject.productTemplate ).toHaveClass( + /columns-2/ + ); + + await pageObject.setNumberOfColumns( 4 ); + await expect( pageObject.productTemplate ).toHaveClass( + /columns-4/ + ); + + await pageObject.publishAndGoToFrontend(); + + await expect( pageObject.productTemplate ).toHaveClass( + /columns-4/ + ); + } ); + + test( 'Order By - sort products by title in descending order correctly', async ( { + pageObject, + } ) => { + await pageObject.createNewPostAndInsertBlock(); + + const sortedTitles = [ + 'WordPress Pennant', + 'V-Neck T-Shirt', + 'T-Shirt with Logo', + 'T-Shirt', + /Sunglasses/, // In the frontend it's "Protected: Sunglasses" + 'Single', + 'Polo', + 'Long Sleeve Tee', + 'Logo Collection', + ]; + + await pageObject.setOrderBy( 'title/desc' ); + await expect( pageObject.productTitles ).toHaveText( sortedTitles ); + + await pageObject.publishAndGoToFrontend(); + await expect( pageObject.productTitles ).toHaveText( sortedTitles ); + } ); + + // Products can be filtered based on 'on sale' status. + test( 'Products can be filtered based on "on sale" status', async ( { + pageObject, + } ) => { + await pageObject.createNewPostAndInsertBlock(); + + let allProducts = pageObject.products; + let saleProducts = pageObject.products.filter( { + hasText: 'Product on sale', + } ); + + await expect( allProducts ).toHaveCount( 9 ); + await expect( saleProducts ).toHaveCount( 6 ); + + await pageObject.setShowOnlyProductsOnSale( { + onSale: true, + } ); + + await expect( allProducts ).toHaveCount( 6 ); + await expect( saleProducts ).toHaveCount( 6 ); + + await pageObject.publishAndGoToFrontend(); + await pageObject.refreshLocators( 'frontend' ); + allProducts = pageObject.products; + saleProducts = pageObject.products.filter( { + hasText: 'Product on sale', + } ); + + await expect( allProducts ).toHaveCount( 6 ); + await expect( saleProducts ).toHaveCount( 6 ); + } ); + + test( 'Products can be filtered based on selection in handpicked products option', async ( { + pageObject, + } ) => { + await pageObject.createNewPostAndInsertBlock(); + + await pageObject.addFilter( 'Show Hand-picked Products' ); + + const filterName = 'Hand-picked Products'; + await pageObject.setFilterComboboxValue( filterName, [ 'Album' ] ); + await expect( pageObject.products ).toHaveCount( 1 ); + + const productNames = [ 'Album', 'Cap' ]; + await pageObject.setFilterComboboxValue( filterName, productNames ); + await expect( pageObject.products ).toHaveCount( 2 ); + await expect( pageObject.productTitles ).toHaveText( productNames ); + + await pageObject.publishAndGoToFrontend(); + await expect( pageObject.products ).toHaveCount( 2 ); + await expect( pageObject.productTitles ).toHaveText( productNames ); + } ); + + test( 'Products can be filtered based on keyword.', async ( { + pageObject, + } ) => { + await pageObject.createNewPostAndInsertBlock(); + + await pageObject.addFilter( 'Keyword' ); + + await pageObject.setKeyword( 'Album' ); + await expect( pageObject.productTitles ).toHaveText( [ 'Album' ] ); + + await pageObject.setKeyword( 'Cap' ); + await expect( pageObject.productTitles ).toHaveText( [ 'Cap' ] ); + + await pageObject.publishAndGoToFrontend(); + await expect( pageObject.productTitles ).toHaveText( [ 'Cap' ] ); + } ); + + test( 'Products can be filtered based on category.', async ( { + pageObject, + } ) => { + await pageObject.createNewPostAndInsertBlock(); + + const filterName = 'Product categories'; + await pageObject.addFilter( 'Show product categories' ); + await pageObject.setFilterComboboxValue( filterName, [ + 'Clothing', + ] ); + await expect( pageObject.productTitles ).toHaveText( [ + 'Logo Collection', + ] ); + + await pageObject.setFilterComboboxValue( filterName, [ + 'Accessories', + ] ); + const accessoriesProductNames = [ + 'Beanie', + 'Beanie with Logo', + 'Belt', + 'Cap', + 'Sunglasses', + ]; + await expect( pageObject.productTitles ).toHaveText( + accessoriesProductNames + ); + + await pageObject.publishAndGoToFrontend(); + + const frontendAccessoriesProductNames = [ + 'Beanie', + 'Beanie with Logo', + 'Belt', + 'Cap', + 'Protected: Sunglasses', + ]; + await expect( pageObject.productTitles ).toHaveText( + frontendAccessoriesProductNames + ); + } ); + + test( 'Products can be filtered based on tags.', async ( { + pageObject, + } ) => { + await pageObject.createNewPostAndInsertBlock(); + + const filterName = 'Product tags'; + await pageObject.addFilter( 'Show product tags' ); + await pageObject.setFilterComboboxValue( filterName, [ + 'Recommended', + ] ); + await expect( pageObject.productTitles ).toHaveText( [ + 'Beanie', + 'Hoodie', + ] ); + + await pageObject.publishAndGoToFrontend(); + await expect( pageObject.productTitles ).toHaveText( [ + 'Beanie', + 'Hoodie', + ] ); + } ); + + test( 'Products can be filtered based on product attributes like color, size etc.', async ( { + pageObject, + } ) => { + await pageObject.createNewPostAndInsertBlock(); + + await pageObject.addFilter( 'Show Product Attributes' ); + await pageObject.setProductAttribute( 'Color', 'Green' ); + + await expect( pageObject.products ).toHaveCount( 3 ); + + await pageObject.setProductAttribute( 'Size', 'Large' ); + + await expect( pageObject.products ).toHaveCount( 1 ); + + await pageObject.publishAndGoToFrontend(); + + await expect( pageObject.products ).toHaveCount( 1 ); + } ); + + test( 'Products can be filtered based on stock status (in stock, out of stock, or backorder).', async ( { + pageObject, + } ) => { + await pageObject.createNewPostAndInsertBlock(); + + await pageObject.setFilterComboboxValue( 'Stock status', [ + 'Out of stock', + ] ); + + await expect( pageObject.productTitles ).toHaveText( [ + 'T-Shirt with Logo', + ] ); + + await pageObject.publishAndGoToFrontend(); + + await expect( pageObject.productTitles ).toHaveText( [ + 'T-Shirt with Logo', + ] ); + } ); + + test( 'Products can be filtered based on featured status.', async ( { + pageObject, + } ) => { + await pageObject.createNewPostAndInsertBlock(); + + await expect( pageObject.products ).toHaveCount( 9 ); + + await pageObject.addFilter( 'Featured' ); + await pageObject.setShowOnlyFeaturedProducts( { + featured: true, + } ); + + // In test data we have only 4 featured products. + await expect( pageObject.products ).toHaveCount( 4 ); + + await pageObject.publishAndGoToFrontend(); + + await expect( pageObject.products ).toHaveCount( 4 ); + } ); + + test( 'Products can be filtered based on created date.', async ( { + pageObject, + } ) => { + await pageObject.createNewPostAndInsertBlock(); + + await expect( pageObject.products ).toHaveCount( 9 ); + + await pageObject.addFilter( 'Created' ); + await pageObject.setCreatedFilter( { + operator: 'within', + range: 'last3months', + } ); + + // Products are created with the fixed publish date back in 2019 + // so there's no products published in last 3 months. + await expect( pageObject.products ).toHaveCount( 0 ); + + await pageObject.setCreatedFilter( { + operator: 'before', + range: 'last3months', + } ); + + await expect( pageObject.products ).toHaveCount( 9 ); + + await pageObject.publishAndGoToFrontend(); + + await expect( pageObject.products ).toHaveCount( 9 ); + } ); + + test( 'Products can be filtered based on price range.', async ( { + pageObject, + } ) => { + await pageObject.createNewPostAndInsertBlock(); + + await expect( pageObject.products ).toHaveCount( 9 ); + + await pageObject.addFilter( 'Price Range' ); + await pageObject.setPriceRange( { + min: '18.33', + } ); + + await expect( pageObject.products ).toHaveCount( 7 ); + + await pageObject.setPriceRange( { + min: '15.28', + max: '17.21', + } ); + + await expect( pageObject.products ).toHaveCount( 1 ); + + await pageObject.setPriceRange( { + max: '17.29', + } ); + + await expect( pageObject.products ).toHaveCount( 4 ); + + await pageObject.publishAndGoToFrontend(); + + await expect( pageObject.products ).toHaveCount( 4 ); + } ); + + // See https://github.com/woocommerce/woocommerce/pull/49917 + test( 'Price range is inclusive in both editor and frontend.', async ( { + page, + pageObject, + editor, + } ) => { + await pageObject.createNewPostAndInsertBlock(); + + await expect( pageObject.products ).toHaveCount( 9 ); + + await pageObject.addFilter( 'Price Range' ); + await pageObject.setPriceRange( { + min: '45', + max: '55', + } ); + + // Wait for the products to be filtered. + await expect( pageObject.products ).not.toHaveCount( 9 ); + + await expect( + pageObject.products.filter( { hasText: '$45.00' } ) + ).not.toHaveCount( 0 ); + await expect( + pageObject.products.filter( { hasText: '$55.00' } ) + ).not.toHaveCount( 0 ); + + // Reset the price range. + await pageObject.setPriceRange( { + min: '0', + max: '0', + } ); + + await expect( pageObject.products ).toHaveCount( 9 ); + + await editor.insertBlock( { + name: 'woocommerce/filter-wrapper', + attributes: { filterType: 'price-filter' }, + } ); + + await pageObject.publishAndGoToFrontend(); + + await expect( pageObject.products ).toHaveCount( 9 ); + + await page + .getByRole( 'textbox', { + name: 'Filter products by minimum', + } ) + .dblclick(); + await page.keyboard.type( '45' ); + + await page + .getByRole( 'textbox', { + name: 'Filter products by maximum', + } ) + .dblclick(); + await page.keyboard.type( '55' ); + + await page.keyboard.press( 'Tab' ); + + // Wait for the products to be filtered. + await expect( pageObject.products ).not.toHaveCount( 9 ); + + await expect( + pageObject.products.filter( { hasText: '$45.00' } ) + ).not.toHaveCount( 0 ); + await expect( + pageObject.products.filter( { hasText: '$55.00' } ) + ).not.toHaveCount( 0 ); + } ); + + test.describe( '"Use page context" control', () => { + test( 'should be visible on posts', async ( { pageObject } ) => { + await pageObject.createNewPostAndInsertBlock(); + + await expect( + pageObject + .locateSidebarSettings() + .locator( SELECTORS.usePageContextControl ) + ).toBeVisible(); + } ); + + [ + 'woocommerce/woocommerce//archive-product', + 'woocommerce/woocommerce//taxonomy-product_cat', + 'woocommerce/woocommerce//taxonomy-product_tag', + 'woocommerce/woocommerce//taxonomy-product_attribute', + 'woocommerce/woocommerce//product-search-results', + ].forEach( ( slug ) => { + test( `should be visible in archive template: ${ slug }`, async ( { + pageObject, + editor, + } ) => { + await pageObject.goToEditorTemplate( slug ); + await pageObject.insertProductCollection(); + await pageObject.chooseCollectionInTemplate(); + await pageObject.focusProductCollection(); + await editor.openDocumentSettingsSidebar(); + + await expect( + pageObject + .locateSidebarSettings() + .locator( SELECTORS.usePageContextControl ) + ).toBeVisible(); + } ); + } ); + + [ + 'woocommerce/woocommerce//single-product', + 'twentytwentyfour//home', + 'twentytwentyfour//index', + ].forEach( ( slug ) => { + test( `should be visible in non-archive template: ${ slug }`, async ( { + pageObject, + editor, + } ) => { + await pageObject.goToEditorTemplate( slug ); + await pageObject.insertProductCollection(); + await pageObject.chooseCollectionInTemplate(); + await pageObject.focusProductCollection(); + await editor.openDocumentSettingsSidebar(); + + await expect( + pageObject + .locateSidebarSettings() + .locator( SELECTORS.usePageContextControl ) + ).toBeVisible(); + } ); + } ); + + test( 'should work as expected in Product Catalog template', async ( { + pageObject, + editor, + } ) => { + await pageObject.goToEditorTemplate(); + await pageObject.focusProductCollection(); + await editor.openDocumentSettingsSidebar(); + + const sidebarSettings = pageObject.locateSidebarSettings(); + + // Inherit query from template should be visible & enabled by default + await expect( + sidebarSettings.locator( SELECTORS.usePageContextControl ) + ).toBeVisible(); + await expect( + sidebarSettings.locator( + `${ SELECTORS.usePageContextControl } input` + ) + ).toBeChecked(); + + // "On sale control" should be hidden when inherit query from template is enabled + await expect( + sidebarSettings.getByLabel( SELECTORS.onSaleControlLabel ) + ).toBeHidden(); + + // "On sale control" should be visible when inherit query from template is disabled + await pageObject.setInheritQueryFromTemplate( false ); + await expect( + sidebarSettings.getByLabel( SELECTORS.onSaleControlLabel ) + ).toBeVisible(); + + // "On sale control" should retain its state when inherit query from template is enabled again + await pageObject.setShowOnlyProductsOnSale( { + onSale: true, + isLocatorsRefreshNeeded: false, + } ); + await expect( + sidebarSettings.getByLabel( SELECTORS.onSaleControlLabel ) + ).toBeChecked(); + await pageObject.setInheritQueryFromTemplate( true ); + await expect( + sidebarSettings.getByLabel( SELECTORS.onSaleControlLabel ) + ).toBeHidden(); + await pageObject.setInheritQueryFromTemplate( false ); + await expect( + sidebarSettings.getByLabel( SELECTORS.onSaleControlLabel ) + ).toBeVisible(); + await expect( + sidebarSettings.getByLabel( SELECTORS.onSaleControlLabel ) + ).toBeChecked(); + } ); + + test( 'is enabled by default unless already enabled elsewhere', async ( { + pageObject, + editor, + } ) => { + const productCollection = editor.canvas.getByLabel( + 'Block: Product Collection', + { exact: true } + ); + const usePageContextToggle = pageObject + .locateSidebarSettings() + .locator( SELECTORS.usePageContextControl ) + .locator( 'input' ); + + // First Product Catalog + // Option should be visible & ENABLED by default + await pageObject.goToEditorTemplate(); + await editor.selectBlocks( productCollection.first() ); + await editor.openDocumentSettingsSidebar(); + + await expect( usePageContextToggle ).toBeChecked(); + + // Second Product Catalog + // Option should be visible & DISABLED by default + await pageObject.insertProductCollection(); + await pageObject.chooseCollectionInTemplate( 'productCatalog' ); + await editor.selectBlocks( productCollection.last() ); + + await expect( usePageContextToggle ).not.toBeChecked(); + + // Disable the option in the first Product Catalog + await editor.selectBlocks( productCollection.first() ); + await usePageContextToggle.click(); + + // Third Product Catalog + // Option should be visible & ENABLED by default + await pageObject.insertProductCollection(); + await pageObject.chooseCollectionInTemplate( 'productCatalog' ); + await editor.selectBlocks( productCollection.last() ); + + await expect( usePageContextToggle ).toBeChecked(); + } ); + + test( 'allows filtering in non-archive context', async ( { + pageObject, + editor, + page, + } ) => { + await pageObject.createNewPostAndInsertBlock(); + + await expect( pageObject.products ).toHaveCount( 9 ); + + await pageObject.insertProductCollection(); + await pageObject.chooseCollectionInPost( 'productCatalog' ); + + await expect( pageObject.products ).toHaveCount( 18 ); + + await page.getByLabel( 'Toggle block inserter' ).click(); + await page.getByRole( 'tab', { name: 'Patterns' } ).click(); + await page + .getByPlaceholder( 'Search' ) + .fill( 'product filters' ); + await page.getByLabel( 'Product Filters' ).click(); + + const postId = await editor.publishPost(); + await page.goto( `/?p=${ postId }` ); + + const productCollection = page.locator( + '.wp-block-woocommerce-product-collection' + ); + + await expect( + productCollection.first().locator( SELECTORS.product ) + ).toHaveCount( 9 ); + await expect( + productCollection.last().locator( SELECTORS.product ) + ).toHaveCount( 9 ); + + await page + .getByRole( 'textbox', { + name: 'Filter products by maximum', + } ) + .dblclick(); + await page.keyboard.type( '10' ); + await page.keyboard.press( 'Tab' ); + + await expect( + productCollection.first().locator( SELECTORS.product ) + ).toHaveCount( 1 ); + await expect( + productCollection.last().locator( SELECTORS.product ) + ).toHaveCount( 9 ); + } ); + + test( 'correctly combines editor and front-end filters', async ( { + pageObject, + editor, + page, + } ) => { + await pageObject.createNewPostAndInsertBlock(); + + await expect( pageObject.products ).toHaveCount( 9 ); + + await pageObject.addFilter( 'Show product categories' ); + await pageObject.setFilterComboboxValue( 'Product categories', [ + 'Music', + ] ); + + await page.getByLabel( 'Toggle block inserter' ).click(); + await page.getByRole( 'tab', { name: 'Patterns' } ).click(); + await page + .getByPlaceholder( 'Search' ) + .fill( 'product filters' ); + await page.getByLabel( 'Product Filters' ).click(); + + await expect( pageObject.products ).toHaveCount( 2 ); + + const postId = await editor.publishPost(); + await page.goto( `/?p=${ postId }` ); + await pageObject.refreshLocators( 'frontend' ); + + await expect( pageObject.products ).toHaveCount( 2 ); + + await page + .getByRole( 'textbox', { + name: 'Filter products by maximum', + } ) + .dblclick(); + await page.keyboard.type( '5' ); + await page.keyboard.press( 'Tab' ); + + await expect( pageObject.products ).toHaveCount( 1 ); + } ); + } ); + } ); +} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.block_theme.spec.ts index dd48320f303..026402747e9 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.block_theme.spec.ts @@ -165,623 +165,6 @@ test.describe( 'Product Collection', () => { } ); } ); - test.describe( 'Inspector Controls', () => { - test( 'Reflects the correct number of columns according to sidebar settings', async ( { - pageObject, - } ) => { - await pageObject.createNewPostAndInsertBlock(); - - await pageObject.setNumberOfColumns( 2 ); - await expect( pageObject.productTemplate ).toHaveClass( - /columns-2/ - ); - - await pageObject.setNumberOfColumns( 4 ); - await expect( pageObject.productTemplate ).toHaveClass( - /columns-4/ - ); - - await pageObject.publishAndGoToFrontend(); - - await expect( pageObject.productTemplate ).toHaveClass( - /columns-4/ - ); - } ); - - test( 'Order By - sort products by title in descending order correctly', async ( { - pageObject, - } ) => { - await pageObject.createNewPostAndInsertBlock(); - - const sortedTitles = [ - 'WordPress Pennant', - 'V-Neck T-Shirt', - 'T-Shirt with Logo', - 'T-Shirt', - /Sunglasses/, // In the frontend it's "Protected: Sunglasses" - 'Single', - 'Polo', - 'Long Sleeve Tee', - 'Logo Collection', - ]; - - await pageObject.setOrderBy( 'title/desc' ); - await expect( pageObject.productTitles ).toHaveText( sortedTitles ); - - await pageObject.publishAndGoToFrontend(); - await expect( pageObject.productTitles ).toHaveText( sortedTitles ); - } ); - - // Products can be filtered based on 'on sale' status. - test( 'Products can be filtered based on "on sale" status', async ( { - pageObject, - } ) => { - await pageObject.createNewPostAndInsertBlock(); - - let allProducts = pageObject.products; - let saleProducts = pageObject.products.filter( { - hasText: 'Product on sale', - } ); - - await expect( allProducts ).toHaveCount( 9 ); - await expect( saleProducts ).toHaveCount( 6 ); - - await pageObject.setShowOnlyProductsOnSale( { - onSale: true, - } ); - - await expect( allProducts ).toHaveCount( 6 ); - await expect( saleProducts ).toHaveCount( 6 ); - - await pageObject.publishAndGoToFrontend(); - await pageObject.refreshLocators( 'frontend' ); - allProducts = pageObject.products; - saleProducts = pageObject.products.filter( { - hasText: 'Product on sale', - } ); - - await expect( allProducts ).toHaveCount( 6 ); - await expect( saleProducts ).toHaveCount( 6 ); - } ); - - test( 'Products can be filtered based on selection in handpicked products option', async ( { - pageObject, - } ) => { - await pageObject.createNewPostAndInsertBlock(); - - await pageObject.addFilter( 'Show Hand-picked Products' ); - - const filterName = 'Hand-picked Products'; - await pageObject.setFilterComboboxValue( filterName, [ 'Album' ] ); - await expect( pageObject.products ).toHaveCount( 1 ); - - const productNames = [ 'Album', 'Cap' ]; - await pageObject.setFilterComboboxValue( filterName, productNames ); - await expect( pageObject.products ).toHaveCount( 2 ); - await expect( pageObject.productTitles ).toHaveText( productNames ); - - await pageObject.publishAndGoToFrontend(); - await expect( pageObject.products ).toHaveCount( 2 ); - await expect( pageObject.productTitles ).toHaveText( productNames ); - } ); - - test( 'Products can be filtered based on keyword.', async ( { - pageObject, - } ) => { - await pageObject.createNewPostAndInsertBlock(); - - await pageObject.addFilter( 'Keyword' ); - - await pageObject.setKeyword( 'Album' ); - await expect( pageObject.productTitles ).toHaveText( [ 'Album' ] ); - - await pageObject.setKeyword( 'Cap' ); - await expect( pageObject.productTitles ).toHaveText( [ 'Cap' ] ); - - await pageObject.publishAndGoToFrontend(); - await expect( pageObject.productTitles ).toHaveText( [ 'Cap' ] ); - } ); - - test( 'Products can be filtered based on category.', async ( { - pageObject, - } ) => { - await pageObject.createNewPostAndInsertBlock(); - - const filterName = 'Product categories'; - await pageObject.addFilter( 'Show product categories' ); - await pageObject.setFilterComboboxValue( filterName, [ - 'Clothing', - ] ); - await expect( pageObject.productTitles ).toHaveText( [ - 'Logo Collection', - ] ); - - await pageObject.setFilterComboboxValue( filterName, [ - 'Accessories', - ] ); - const accessoriesProductNames = [ - 'Beanie', - 'Beanie with Logo', - 'Belt', - 'Cap', - 'Sunglasses', - ]; - await expect( pageObject.productTitles ).toHaveText( - accessoriesProductNames - ); - - await pageObject.publishAndGoToFrontend(); - - const frontendAccessoriesProductNames = [ - 'Beanie', - 'Beanie with Logo', - 'Belt', - 'Cap', - 'Protected: Sunglasses', - ]; - await expect( pageObject.productTitles ).toHaveText( - frontendAccessoriesProductNames - ); - } ); - - test( 'Products can be filtered based on tags.', async ( { - pageObject, - } ) => { - await pageObject.createNewPostAndInsertBlock(); - - const filterName = 'Product tags'; - await pageObject.addFilter( 'Show product tags' ); - await pageObject.setFilterComboboxValue( filterName, [ - 'Recommended', - ] ); - await expect( pageObject.productTitles ).toHaveText( [ - 'Beanie', - 'Hoodie', - ] ); - - await pageObject.publishAndGoToFrontend(); - await expect( pageObject.productTitles ).toHaveText( [ - 'Beanie', - 'Hoodie', - ] ); - } ); - - test( 'Products can be filtered based on product attributes like color, size etc.', async ( { - pageObject, - } ) => { - await pageObject.createNewPostAndInsertBlock(); - - await pageObject.addFilter( 'Show Product Attributes' ); - await pageObject.setProductAttribute( 'Color', 'Green' ); - - await expect( pageObject.products ).toHaveCount( 3 ); - - await pageObject.setProductAttribute( 'Size', 'Large' ); - - await expect( pageObject.products ).toHaveCount( 1 ); - - await pageObject.publishAndGoToFrontend(); - - await expect( pageObject.products ).toHaveCount( 1 ); - } ); - - test( 'Products can be filtered based on stock status (in stock, out of stock, or backorder).', async ( { - pageObject, - } ) => { - await pageObject.createNewPostAndInsertBlock(); - - await pageObject.setFilterComboboxValue( 'Stock status', [ - 'Out of stock', - ] ); - - await expect( pageObject.productTitles ).toHaveText( [ - 'T-Shirt with Logo', - ] ); - - await pageObject.publishAndGoToFrontend(); - - await expect( pageObject.productTitles ).toHaveText( [ - 'T-Shirt with Logo', - ] ); - } ); - - test( 'Products can be filtered based on featured status.', async ( { - pageObject, - } ) => { - await pageObject.createNewPostAndInsertBlock(); - - await expect( pageObject.products ).toHaveCount( 9 ); - - await pageObject.addFilter( 'Featured' ); - await pageObject.setShowOnlyFeaturedProducts( { - featured: true, - } ); - - // In test data we have only 4 featured products. - await expect( pageObject.products ).toHaveCount( 4 ); - - await pageObject.publishAndGoToFrontend(); - - await expect( pageObject.products ).toHaveCount( 4 ); - } ); - - test( 'Products can be filtered based on created date.', async ( { - pageObject, - } ) => { - await pageObject.createNewPostAndInsertBlock(); - - await expect( pageObject.products ).toHaveCount( 9 ); - - await pageObject.addFilter( 'Created' ); - await pageObject.setCreatedFilter( { - operator: 'within', - range: 'last3months', - } ); - - // Products are created with the fixed publish date back in 2019 - // so there's no products published in last 3 months. - await expect( pageObject.products ).toHaveCount( 0 ); - - await pageObject.setCreatedFilter( { - operator: 'before', - range: 'last3months', - } ); - - await expect( pageObject.products ).toHaveCount( 9 ); - - await pageObject.publishAndGoToFrontend(); - - await expect( pageObject.products ).toHaveCount( 9 ); - } ); - - test( 'Products can be filtered based on price range.', async ( { - pageObject, - } ) => { - await pageObject.createNewPostAndInsertBlock(); - - await expect( pageObject.products ).toHaveCount( 9 ); - - await pageObject.addFilter( 'Price Range' ); - await pageObject.setPriceRange( { - min: '18.33', - } ); - - await expect( pageObject.products ).toHaveCount( 7 ); - - await pageObject.setPriceRange( { - min: '15.28', - max: '17.21', - } ); - - await expect( pageObject.products ).toHaveCount( 1 ); - - await pageObject.setPriceRange( { - max: '17.29', - } ); - - await expect( pageObject.products ).toHaveCount( 4 ); - - await pageObject.publishAndGoToFrontend(); - - await expect( pageObject.products ).toHaveCount( 4 ); - } ); - - // See https://github.com/woocommerce/woocommerce/pull/49917 - test( 'Price range is inclusive in both editor and frontend.', async ( { - page, - pageObject, - editor, - } ) => { - await pageObject.createNewPostAndInsertBlock(); - - await expect( pageObject.products ).toHaveCount( 9 ); - - await pageObject.addFilter( 'Price Range' ); - await pageObject.setPriceRange( { - min: '45', - max: '55', - } ); - - // Wait for the products to be filtered. - await expect( pageObject.products ).not.toHaveCount( 9 ); - - await expect( - pageObject.products.filter( { hasText: '$45.00' } ) - ).not.toHaveCount( 0 ); - await expect( - pageObject.products.filter( { hasText: '$55.00' } ) - ).not.toHaveCount( 0 ); - - // Reset the price range. - await pageObject.setPriceRange( { - min: '0', - max: '0', - } ); - - await expect( pageObject.products ).toHaveCount( 9 ); - - await editor.insertBlock( { - name: 'woocommerce/filter-wrapper', - attributes: { filterType: 'price-filter' }, - } ); - - await pageObject.publishAndGoToFrontend(); - - await expect( pageObject.products ).toHaveCount( 9 ); - - await page - .getByRole( 'textbox', { - name: 'Filter products by minimum', - } ) - .dblclick(); - await page.keyboard.type( '45' ); - - await page - .getByRole( 'textbox', { - name: 'Filter products by maximum', - } ) - .dblclick(); - await page.keyboard.type( '55' ); - - await page.keyboard.press( 'Tab' ); - - // Wait for the products to be filtered. - await expect( pageObject.products ).not.toHaveCount( 9 ); - - await expect( - pageObject.products.filter( { hasText: '$45.00' } ) - ).not.toHaveCount( 0 ); - await expect( - pageObject.products.filter( { hasText: '$55.00' } ) - ).not.toHaveCount( 0 ); - } ); - - test.describe( '"Use page context" control', () => { - test( 'should be visible on posts', async ( { pageObject } ) => { - await pageObject.createNewPostAndInsertBlock(); - - await expect( - pageObject - .locateSidebarSettings() - .locator( SELECTORS.usePageContextControl ) - ).toBeVisible(); - } ); - - [ - 'woocommerce/woocommerce//archive-product', - 'woocommerce/woocommerce//taxonomy-product_cat', - 'woocommerce/woocommerce//taxonomy-product_tag', - 'woocommerce/woocommerce//taxonomy-product_attribute', - 'woocommerce/woocommerce//product-search-results', - ].forEach( ( slug ) => { - test( `should be visible in archive template: ${ slug }`, async ( { - pageObject, - editor, - } ) => { - await pageObject.goToEditorTemplate( slug ); - await pageObject.insertProductCollection(); - await pageObject.chooseCollectionInTemplate(); - await pageObject.focusProductCollection(); - await editor.openDocumentSettingsSidebar(); - - await expect( - pageObject - .locateSidebarSettings() - .locator( SELECTORS.usePageContextControl ) - ).toBeVisible(); - } ); - } ); - - [ - 'woocommerce/woocommerce//single-product', - 'twentytwentyfour//home', - 'twentytwentyfour//index', - ].forEach( ( slug ) => { - test( `should be visible in non-archive template: ${ slug }`, async ( { - pageObject, - editor, - } ) => { - await pageObject.goToEditorTemplate( slug ); - await pageObject.insertProductCollection(); - await pageObject.chooseCollectionInTemplate(); - await pageObject.focusProductCollection(); - await editor.openDocumentSettingsSidebar(); - - await expect( - pageObject - .locateSidebarSettings() - .locator( SELECTORS.usePageContextControl ) - ).toBeVisible(); - } ); - } ); - - test( 'should work as expected in Product Catalog template', async ( { - pageObject, - editor, - } ) => { - await pageObject.goToEditorTemplate(); - await pageObject.focusProductCollection(); - await editor.openDocumentSettingsSidebar(); - - const sidebarSettings = pageObject.locateSidebarSettings(); - - // Inherit query from template should be visible & enabled by default - await expect( - sidebarSettings.locator( SELECTORS.usePageContextControl ) - ).toBeVisible(); - await expect( - sidebarSettings.locator( - `${ SELECTORS.usePageContextControl } input` - ) - ).toBeChecked(); - - // "On sale control" should be hidden when inherit query from template is enabled - await expect( - sidebarSettings.getByLabel( SELECTORS.onSaleControlLabel ) - ).toBeHidden(); - - // "On sale control" should be visible when inherit query from template is disabled - await pageObject.setInheritQueryFromTemplate( false ); - await expect( - sidebarSettings.getByLabel( SELECTORS.onSaleControlLabel ) - ).toBeVisible(); - - // "On sale control" should retain its state when inherit query from template is enabled again - await pageObject.setShowOnlyProductsOnSale( { - onSale: true, - isLocatorsRefreshNeeded: false, - } ); - await expect( - sidebarSettings.getByLabel( SELECTORS.onSaleControlLabel ) - ).toBeChecked(); - await pageObject.setInheritQueryFromTemplate( true ); - await expect( - sidebarSettings.getByLabel( SELECTORS.onSaleControlLabel ) - ).toBeHidden(); - await pageObject.setInheritQueryFromTemplate( false ); - await expect( - sidebarSettings.getByLabel( SELECTORS.onSaleControlLabel ) - ).toBeVisible(); - await expect( - sidebarSettings.getByLabel( SELECTORS.onSaleControlLabel ) - ).toBeChecked(); - } ); - - test( 'is enabled by default unless already enabled elsewhere', async ( { - pageObject, - editor, - } ) => { - const productCollection = editor.canvas.getByLabel( - 'Block: Product Collection', - { exact: true } - ); - const usePageContextToggle = pageObject - .locateSidebarSettings() - .locator( SELECTORS.usePageContextControl ) - .locator( 'input' ); - - // First Product Catalog - // Option should be visible & ENABLED by default - await pageObject.goToEditorTemplate(); - await editor.selectBlocks( productCollection.first() ); - await editor.openDocumentSettingsSidebar(); - - await expect( usePageContextToggle ).toBeChecked(); - - // Second Product Catalog - // Option should be visible & DISABLED by default - await pageObject.insertProductCollection(); - await pageObject.chooseCollectionInTemplate( 'productCatalog' ); - await editor.selectBlocks( productCollection.last() ); - - await expect( usePageContextToggle ).not.toBeChecked(); - - // Disable the option in the first Product Catalog - await editor.selectBlocks( productCollection.first() ); - await usePageContextToggle.click(); - - // Third Product Catalog - // Option should be visible & ENABLED by default - await pageObject.insertProductCollection(); - await pageObject.chooseCollectionInTemplate( 'productCatalog' ); - await editor.selectBlocks( productCollection.last() ); - - await expect( usePageContextToggle ).toBeChecked(); - } ); - - test( 'allows filtering in non-archive context', async ( { - pageObject, - editor, - page, - } ) => { - await pageObject.createNewPostAndInsertBlock(); - - await expect( pageObject.products ).toHaveCount( 9 ); - - await pageObject.insertProductCollection(); - await pageObject.chooseCollectionInPost( 'productCatalog' ); - - await expect( pageObject.products ).toHaveCount( 18 ); - - await page.getByLabel( 'Toggle block inserter' ).click(); - await page.getByRole( 'tab', { name: 'Patterns' } ).click(); - await page - .getByPlaceholder( 'Search' ) - .fill( 'product filters' ); - await page.getByLabel( 'Product Filters' ).click(); - - const postId = await editor.publishPost(); - await page.goto( `/?p=${ postId }` ); - - const productCollection = page.locator( - '.wp-block-woocommerce-product-collection' - ); - - await expect( - productCollection.first().locator( SELECTORS.product ) - ).toHaveCount( 9 ); - await expect( - productCollection.last().locator( SELECTORS.product ) - ).toHaveCount( 9 ); - - await page - .getByRole( 'textbox', { - name: 'Filter products by maximum', - } ) - .dblclick(); - await page.keyboard.type( '10' ); - await page.keyboard.press( 'Tab' ); - - await expect( - productCollection.first().locator( SELECTORS.product ) - ).toHaveCount( 1 ); - await expect( - productCollection.last().locator( SELECTORS.product ) - ).toHaveCount( 9 ); - } ); - - test( 'correctly combines editor and front-end filters', async ( { - pageObject, - editor, - page, - } ) => { - await pageObject.createNewPostAndInsertBlock(); - - await expect( pageObject.products ).toHaveCount( 9 ); - - await pageObject.addFilter( 'Show product categories' ); - await pageObject.setFilterComboboxValue( 'Product categories', [ - 'Music', - ] ); - - await page.getByLabel( 'Toggle block inserter' ).click(); - await page.getByRole( 'tab', { name: 'Patterns' } ).click(); - await page - .getByPlaceholder( 'Search' ) - .fill( 'product filters' ); - await page.getByLabel( 'Product Filters' ).click(); - - await expect( pageObject.products ).toHaveCount( 2 ); - - const postId = await editor.publishPost(); - await page.goto( `/?p=${ postId }` ); - await pageObject.refreshLocators( 'frontend' ); - - await expect( pageObject.products ).toHaveCount( 2 ); - - await page - .getByRole( 'textbox', { - name: 'Filter products by maximum', - } ) - .dblclick(); - await page.keyboard.type( '5' ); - await page.keyboard.press( 'Tab' ); - - await expect( pageObject.products ).toHaveCount( 1 ); - } ); - } ); - } ); - test.describe( 'Toolbar settings', () => { test.beforeEach( async ( { pageObject } ) => { await pageObject.createNewPostAndInsertBlock(); diff --git a/plugins/woocommerce/changelog/50396-48150-move-products-endpoint b/plugins/woocommerce/changelog/50396-48150-move-products-endpoint new file mode 100644 index 00000000000..3a397780d9a --- /dev/null +++ b/plugins/woocommerce/changelog/50396-48150-move-products-endpoint @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +CYS - Move the "ai/products" endpoint to woocommerce admin API. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/50770-image-add-additional-size-units b/plugins/woocommerce/changelog/50770-image-add-additional-size-units new file mode 100644 index 00000000000..f67f858fac8 --- /dev/null +++ b/plugins/woocommerce/changelog/50770-image-add-additional-size-units @@ -0,0 +1,4 @@ +Significance: patch +Type: enhancement + +Add additional sizing units for product image block \ No newline at end of file diff --git a/plugins/woocommerce/changelog/50784-add-coming-soon-mode-e2e b/plugins/woocommerce/changelog/50784-add-coming-soon-mode-e2e new file mode 100644 index 00000000000..bfa1b361fd6 --- /dev/null +++ b/plugins/woocommerce/changelog/50784-add-coming-soon-mode-e2e @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Add e2e tests to confirm that the store is in coming soon mode after completing the core profiler \ No newline at end of file diff --git a/plugins/woocommerce/changelog/50828-dev-constrain-pnpm-version b/plugins/woocommerce/changelog/50828-dev-constrain-pnpm-version new file mode 100644 index 00000000000..fdead95ceb5 --- /dev/null +++ b/plugins/woocommerce/changelog/50828-dev-constrain-pnpm-version @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Fix pnpm version to 9.1.3 to avoid dependency installation issues. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/fix-42064-product-reviews-endpoint-tests b/plugins/woocommerce/changelog/fix-42064-product-reviews-endpoint-tests new file mode 100644 index 00000000000..8193d364b24 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-42064-product-reviews-endpoint-tests @@ -0,0 +1,5 @@ +Significance: patch +Type: dev +Comment: Store API: Add test coverage for Product Reviews endpoint + + diff --git a/plugins/woocommerce/changelog/fix-conditionally-set-new-order-metafield b/plugins/woocommerce/changelog/fix-conditionally-set-new-order-metafield new file mode 100644 index 00000000000..40da44a369b --- /dev/null +++ b/plugins/woocommerce/changelog/fix-conditionally-set-new-order-metafield @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Conditionally set new order email sent meta field diff --git a/plugins/woocommerce/changelog/test-50444-inspector-control b/plugins/woocommerce/changelog/test-50444-inspector-control new file mode 100644 index 00000000000..fb469b0c0ad --- /dev/null +++ b/plugins/woocommerce/changelog/test-50444-inspector-control @@ -0,0 +1,4 @@ +Significance: minor +Type: tweak + +Move the inspector controls e2e tests from product collection file to its own file diff --git a/plugins/woocommerce/changelog/tweak-improve-reset-password-check b/plugins/woocommerce/changelog/tweak-improve-reset-password-check new file mode 100644 index 00000000000..4f657552c2d --- /dev/null +++ b/plugins/woocommerce/changelog/tweak-improve-reset-password-check @@ -0,0 +1,4 @@ +Significance: patch +Type: tweak + +Update reset password e2e to use or locator to check reset status diff --git a/plugins/woocommerce/changelog/update-contribution-docs b/plugins/woocommerce/changelog/update-contribution-docs new file mode 100644 index 00000000000..0712915d3bc --- /dev/null +++ b/plugins/woocommerce/changelog/update-contribution-docs @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +fix small lint errors and fix lint rule to make doc contribution easier diff --git a/plugins/woocommerce/composer.json b/plugins/woocommerce/composer.json index 52052c43651..b51f7e3c195 100644 --- a/plugins/woocommerce/composer.json +++ b/plugins/woocommerce/composer.json @@ -2,7 +2,7 @@ "name": "woocommerce/woocommerce", "description": "An eCommerce toolkit that helps you sell anything. Beautifully.", "homepage": "https://woocommerce.com/", - "version": "9.3.0", + "version": "9.4.0", "type": "wordpress-plugin", "license": "GPL-3.0-or-later", "prefer-stable": true, diff --git a/plugins/woocommerce/includes/class-woocommerce.php b/plugins/woocommerce/includes/class-woocommerce.php index b2ecf887b0f..41104fcbd65 100644 --- a/plugins/woocommerce/includes/class-woocommerce.php +++ b/plugins/woocommerce/includes/class-woocommerce.php @@ -46,7 +46,7 @@ final class WooCommerce { * * @var string */ - public $version = '9.3.0'; + public $version = '9.4.0'; /** * WooCommerce Schema version. diff --git a/plugins/woocommerce/includes/emails/class-wc-email-new-order.php b/plugins/woocommerce/includes/emails/class-wc-email-new-order.php index 3451c64b3a3..33a49ebce34 100644 --- a/plugins/woocommerce/includes/emails/class-wc-email-new-order.php +++ b/plugins/woocommerce/includes/emails/class-wc-email-new-order.php @@ -109,10 +109,11 @@ if ( ! class_exists( 'WC_Email_New_Order' ) ) : } if ( $this->is_enabled() && $this->get_recipient() ) { - $this->send( $this->get_recipient(), $this->get_subject(), $this->get_content(), $this->get_headers(), $this->get_attachments() ); - - $order->update_meta_data( '_new_order_email_sent', 'true' ); - $order->save(); + $email_sent_successfully = $this->send( $this->get_recipient(), $this->get_subject(), $this->get_content(), $this->get_headers(), $this->get_attachments() ); + if ( $email_sent_successfully ) { + $order->update_meta_data( '_new_order_email_sent', 'true' ); + $order->save(); + } } $this->restore_locale(); diff --git a/plugins/woocommerce/package.json b/plugins/woocommerce/package.json index 5abbacaef70..7ed07ba6aba 100644 --- a/plugins/woocommerce/package.json +++ b/plugins/woocommerce/package.json @@ -2,7 +2,7 @@ "name": "@woocommerce/plugin-woocommerce", "private": true, "title": "WooCommerce", - "version": "9.3.0", + "version": "9.4.0", "homepage": "https://woocommerce.com/", "repository": { "type": "git", @@ -639,7 +639,7 @@ }, "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "browserslist": [ "> 0.1%", diff --git a/plugins/woocommerce/readme.txt b/plugins/woocommerce/readme.txt index bbc9093ada2..04146902c1c 100644 --- a/plugins/woocommerce/readme.txt +++ b/plugins/woocommerce/readme.txt @@ -169,6 +169,6 @@ WooCommerce comes with some sample data you can use to see how products look; im == Changelog == -= 9.3.0 2024-XX-XX = += 9.4.0 2024-XX-XX = [See changelog for all versions](https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/changelog.txt). diff --git a/plugins/woocommerce/src/Admin/API/AI/Products.php b/plugins/woocommerce/src/Admin/API/AI/Products.php new file mode 100644 index 00000000000..e764777ca76 --- /dev/null +++ b/plugins/woocommerce/src/Admin/API/AI/Products.php @@ -0,0 +1,128 @@ +register( + array( + array( + 'methods' => \WP_REST_Server::CREATABLE, + 'callback' => array( $this, 'generate_products_content' ), + 'permission_callback' => array( Middleware::class, 'is_authorized' ), + 'args' => array( + 'business_description' => array( + 'description' => __( 'The business description for a given store.', 'woocommerce' ), + 'type' => 'string', + ), + 'images' => array( + 'description' => __( 'The images for a given store.', 'woocommerce' ), + 'type' => 'object', + ), + ), + ), + array( + 'methods' => \WP_REST_Server::DELETABLE, + 'callback' => array( $this, 'delete_products' ), + 'permission_callback' => array( Middleware::class, 'is_authorized' ), + ), + ) + ); + } + + /** + * Generate the content for the products. + * + * @param WP_REST_Request $request Request object. + * + * @return WP_Error|WP_REST_Response + */ + public function generate_products_content( WP_REST_Request $request ) { + $allow_ai_connection = get_option( 'woocommerce_blocks_allow_ai_connection' ); + + if ( ! $allow_ai_connection ) { + return rest_ensure_response( + new WP_Error( + 'ai_connection_not_allowed', + __( 'AI content generation is not allowed on this store. Update your store settings if you wish to enable this feature.', 'woocommerce' ) + ) + ); + } + + $business_description = sanitize_text_field( wp_unslash( $request['business_description'] ) ); + + if ( empty( $business_description ) ) { + $business_description = get_option( 'woo_ai_describe_store_description' ); + } + + $ai_connection = new Connection(); + + $site_id = $ai_connection->get_site_id(); + + if ( is_wp_error( $site_id ) ) { + return $site_id; + } + + $token = $ai_connection->get_jwt_token( $site_id ); + + if ( is_wp_error( $token ) ) { + return $token; + } + + $images = $request['images']; + + $populate_products = ( new UpdateProducts() )->generate_content( $ai_connection, $token, $images, $business_description ); + + if ( is_wp_error( $populate_products ) ) { + return $populate_products; + } + + if ( ! isset( $populate_products['product_content'] ) ) { + return new WP_Error( 'product_content_not_found', __( 'Product content not found.', 'woocommerce' ) ); + } + + $product_content = $populate_products['product_content']; + + $item = array( + 'ai_content_generated' => true, + 'product_content' => $product_content, + ); + + return rest_ensure_response( $item ); + } + + /** + * Remove products generated by AI. + * + * @return WP_Error|WP_REST_Response + */ + public function delete_products() { + ( new UpdateProducts() )->reset_products_content(); + return rest_ensure_response( array( 'removed' => true ) ); + } +} diff --git a/plugins/woocommerce/src/Admin/API/Init.php b/plugins/woocommerce/src/Admin/API/Init.php index 9ea331a9ef7..65522e28501 100644 --- a/plugins/woocommerce/src/Admin/API/Init.php +++ b/plugins/woocommerce/src/Admin/API/Init.php @@ -92,6 +92,7 @@ class Init { 'Automattic\WooCommerce\Admin\API\AI\Images', 'Automattic\WooCommerce\Admin\API\AI\Patterns', 'Automattic\WooCommerce\Admin\API\AI\Product', + 'Automattic\WooCommerce\Admin\API\AI\Products', ); } diff --git a/plugins/woocommerce/src/StoreApi/Routes/V1/AI/Middleware.php b/plugins/woocommerce/src/StoreApi/Routes/V1/AI/Middleware.php deleted file mode 100644 index 33a2d8ece7f..00000000000 --- a/plugins/woocommerce/src/StoreApi/Routes/V1/AI/Middleware.php +++ /dev/null @@ -1,50 +0,0 @@ -getErrorCode(), - $error->getMessage(), - array( 'status' => $error->getCode() ) - ); - } - - $allow_ai_connection = get_option( 'woocommerce_blocks_allow_ai_connection' ); - - if ( ! $allow_ai_connection ) { - try { - throw new RouteException( 'ai_connection_not_allowed', __( 'AI content generation is not allowed on this store. Update your store settings if you wish to enable this feature.', 'woocommerce' ), 403 ); - } catch ( RouteException $error ) { - return new \WP_Error( - $error->getErrorCode(), - $error->getMessage(), - array( 'status' => $error->getCode() ) - ); - } - } - - return true; - } -} diff --git a/plugins/woocommerce/src/StoreApi/Routes/V1/AI/Products.php b/plugins/woocommerce/src/StoreApi/Routes/V1/AI/Products.php deleted file mode 100644 index 086b8a61636..00000000000 --- a/plugins/woocommerce/src/StoreApi/Routes/V1/AI/Products.php +++ /dev/null @@ -1,153 +0,0 @@ - \WP_REST_Server::CREATABLE, - 'callback' => [ $this, 'get_response' ], - 'permission_callback' => [ Middleware::class, 'is_authorized' ], - 'args' => [ - 'business_description' => [ - 'description' => __( 'The business description for a given store.', 'woocommerce' ), - 'type' => 'string', - ], - 'images' => [ - 'description' => __( 'The images for a given store.', 'woocommerce' ), - 'type' => 'object', - ], - ], - ], - [ - 'methods' => \WP_REST_Server::DELETABLE, - 'callback' => [ $this, 'get_response' ], - 'permission_callback' => [ Middleware::class, 'is_authorized' ], - ], - 'schema' => [ $this->schema, 'get_public_item_schema' ], - 'allow_batch' => [ 'v1' => true ], - ]; - } - - /** - * Generate the content for the products. - * - * @param \WP_REST_Request $request Request object. - * - * @return bool|string|\WP_Error|\WP_REST_Response - */ - protected function get_route_post_response( \WP_REST_Request $request ) { - $allow_ai_connection = get_option( 'woocommerce_blocks_allow_ai_connection' ); - - if ( ! $allow_ai_connection ) { - return rest_ensure_response( - $this->error_to_response( - new \WP_Error( - 'ai_connection_not_allowed', - __( 'AI content generation is not allowed on this store. Update your store settings if you wish to enable this feature.', 'woocommerce' ) - ) - ) - ); - } - - $business_description = sanitize_text_field( wp_unslash( $request['business_description'] ) ); - - if ( empty( $business_description ) ) { - $business_description = get_option( 'woo_ai_describe_store_description' ); - } - - $ai_connection = new Connection(); - - $site_id = $ai_connection->get_site_id(); - - if ( is_wp_error( $site_id ) ) { - return $this->error_to_response( $site_id ); - } - - $token = $ai_connection->get_jwt_token( $site_id ); - - if ( is_wp_error( $token ) ) { - return $this->error_to_response( $token ); - } - - $images = $request['images']; - - $populate_products = ( new UpdateProducts() )->generate_content( $ai_connection, $token, $images, $business_description ); - - if ( is_wp_error( $populate_products ) ) { - return $this->error_to_response( $populate_products ); - } - - if ( ! isset( $populate_products['product_content'] ) ) { - return $this->error_to_response( new \WP_Error( 'product_content_not_found', __( 'Product content not found.', 'woocommerce' ) ) ); - } - - $product_content = $populate_products['product_content']; - - $item = array( - 'ai_content_generated' => true, - 'product_content' => $product_content, - ); - - return rest_ensure_response( $item ); - } - - /** - * Remove products generated by AI. - * - * @param \WP_REST_Request $request Request object. - * - * @return bool|string|\WP_Error|\WP_REST_Response - */ - protected function get_route_delete_response( \WP_REST_Request $request ) { - ( new UpdateProducts() )->reset_products_content(); - return rest_ensure_response( array( 'removed' => true ) ); - } -} diff --git a/plugins/woocommerce/src/StoreApi/RoutesController.php b/plugins/woocommerce/src/StoreApi/RoutesController.php index 1b88b70daa0..5ae47b7ba75 100644 --- a/plugins/woocommerce/src/StoreApi/RoutesController.php +++ b/plugins/woocommerce/src/StoreApi/RoutesController.php @@ -66,10 +66,8 @@ class RoutesController { Routes\V1\ProductsById::IDENTIFIER => Routes\V1\ProductsById::class, Routes\V1\ProductsBySlug::IDENTIFIER => Routes\V1\ProductsBySlug::class, ], - // @todo Migrate internal AI routes to WooCommerce Core codebase. 'private' => [ - Routes\V1\AI\Products::IDENTIFIER => Routes\V1\AI\Products::class, - Routes\V1\Patterns::IDENTIFIER => Routes\V1\Patterns::class, + Routes\V1\Patterns::IDENTIFIER => Routes\V1\Patterns::class, ], ]; } diff --git a/plugins/woocommerce/src/StoreApi/SchemaController.php b/plugins/woocommerce/src/StoreApi/SchemaController.php index 252b475b584..f0a4a4b4718 100644 --- a/plugins/woocommerce/src/StoreApi/SchemaController.php +++ b/plugins/woocommerce/src/StoreApi/SchemaController.php @@ -54,7 +54,6 @@ class SchemaController { Schemas\V1\ProductCategorySchema::IDENTIFIER => Schemas\V1\ProductCategorySchema::class, Schemas\V1\ProductCollectionDataSchema::IDENTIFIER => Schemas\V1\ProductCollectionDataSchema::class, Schemas\V1\ProductReviewSchema::IDENTIFIER => Schemas\V1\ProductReviewSchema::class, - Schemas\V1\AI\ProductsSchema::IDENTIFIER => Schemas\V1\AI\ProductsSchema::class, Schemas\V1\PatternsSchema::IDENTIFIER => Schemas\V1\PatternsSchema::class, ], ]; diff --git a/plugins/woocommerce/src/StoreApi/Schemas/V1/AI/ProductsSchema.php b/plugins/woocommerce/src/StoreApi/Schemas/V1/AI/ProductsSchema.php deleted file mode 100644 index 114df7807af..00000000000 --- a/plugins/woocommerce/src/StoreApi/Schemas/V1/AI/ProductsSchema.php +++ /dev/null @@ -1,48 +0,0 @@ - $item['ai_content_generated'], - 'product_content' => $item['product_content'], - ]; - } -} diff --git a/plugins/woocommerce/tests/e2e-pw/tests/activate-and-setup/core-profiler.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/activate-and-setup/core-profiler.spec.js index 0f4204f2075..e2f3718c0f5 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/activate-and-setup/core-profiler.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/activate-and-setup/core-profiler.spec.js @@ -1,8 +1,22 @@ -const { test, expect } = require( '@playwright/test' ); +const { test, expect, request } = require( '@playwright/test' ); +const { setOption } = require( '../../utils/options' ); test.describe( 'Store owner can complete the core profiler', () => { test.use( { storageState: process.env.ADMINSTATE } ); + test.beforeAll( async ( { baseURL } ) => { + try { + await setOption( + request, + baseURL, + 'woocommerce_coming_soon', + 'no' + ); + } catch ( error ) { + console.log( error ); + } + } ); + test( 'Can complete the core profiler skipping extension install', async ( { page, } ) => { @@ -393,12 +407,32 @@ test.describe( 'Store owner can complete the core profiler', () => { page.getByLabel( 'Delete Pinterest for' ) ).toBeHidden(); } ); + + await test.step( 'Confirm that the store is in coming soon mode after completing the core profiler', async () => { + await page.goto( 'wp-admin/admin.php?page=wc-admin' ); + await expect( + page.getByRole( 'menuitem', { name: 'Store coming soon' } ) + ).toBeVisible(); + } ); } ); } ); test.describe( 'Store owner can skip the core profiler', () => { test.use( { storageState: process.env.ADMINSTATE } ); + test.beforeAll( async ( { baseURL } ) => { + try { + await setOption( + request, + baseURL, + 'woocommerce_coming_soon', + 'no' + ); + } catch ( error ) { + console.log( error ); + } + } ); + test( 'Can click skip guided setup', async ( { page } ) => { await page.goto( 'wp-admin/admin.php?page=wc-admin&path=%2Fsetup-wizard' @@ -426,6 +460,13 @@ test.describe( 'Store owner can skip the core profiler', () => { name: 'Welcome to WooCommerce Core E2E Test Suite', } ) ).toBeVisible(); + + await test.step( 'Confirm that the store is in coming soon mode after skipping the core profiler', async () => { + await page.goto( 'wp-admin/admin.php?page=wc-admin' ); + await expect( + page.getByRole( 'menuitem', { name: 'Store coming soon' } ) + ).toBeVisible(); + } ); } ); test( 'Can connect to WooCommerce.com', async ( { page } ) => { diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/lost-password.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/lost-password.spec.js index e13f2d7c9ba..c38e848c541 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/lost-password.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/lost-password.spec.js @@ -19,20 +19,16 @@ test.describe( 'Can go to lost password page and submit the form', () => { .fill( admin.username ); await page.getByRole( 'button', { name: 'Get New Password' } ).click(); - try { - // For local testing, the email might not be sent, so we can ignore this error. - await expect( - page.getByText( - /The email could not be sent. Your site may not be correctly configured to send emails/i - ) - ).toBeVisible(); - } catch ( e ) { - // eslint-disable-next-line jest/no-try-expect - await page.waitForURL( '**/wp-login.php?checkemail=confirm' ); - // eslint-disable-next-line jest/no-try-expect - await expect( - page.getByText( /Check your email for the confirmation link/i ) - ).toBeVisible(); - } + const emailSentMessage = page.getByText( + 'check your email for the confirmation link' + ); + const emailNotSentMessage = page.getByText( + 'the email could not be sent' + ); + + // We don't have to care if the email was sent, we just want to know the button click attempts a reset. + await expect( + emailSentMessage.or( emailNotSentMessage ) + ).toBeVisible(); } ); } ); diff --git a/plugins/woocommerce/tests/php/src/Blocks/Helpers/FixtureData.php b/plugins/woocommerce/tests/php/src/Blocks/Helpers/FixtureData.php index f1342196129..e31a24cf7ea 100644 --- a/plugins/woocommerce/tests/php/src/Blocks/Helpers/FixtureData.php +++ b/plugins/woocommerce/tests/php/src/Blocks/Helpers/FixtureData.php @@ -194,6 +194,22 @@ class FixtureData { return $return; } + /** + * Create a product category and return the result. + * + * @param array $props Category props. + * @return array + */ + public function get_product_category( $props ) { + $category_name = $props['name'] ?? 'Test Category'; + + return wp_insert_term( + $category_name, + 'product_cat', + $props + ); + } + /** * Create a coupon and return the result. * diff --git a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/ProductReviews.php b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/ProductReviews.php new file mode 100644 index 00000000000..e11497c5cbb --- /dev/null +++ b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/ProductReviews.php @@ -0,0 +1,129 @@ +product_category = $fixtures->get_product_category( + array( + 'name' => 'Test Category 1', + ) + ); + + $this->products = array( + $fixtures->get_simple_product( + array( + 'name' => 'Test Product 1', + 'regular_price' => 10, + ) + ), + $fixtures->get_simple_product( + array( + 'name' => 'Test Product 2', + 'regular_price' => 100, + 'category_ids' => array( $this->product_category['term_id'] ), + ) + ), + ); + + $fixtures->add_product_review( $this->products[0]->get_id(), 5 ); + $fixtures->add_product_review( $this->products[1]->get_id(), 4 ); + } + + /** + * Test getting reviews. + */ + public function test_get_items() { + $response = rest_get_server()->dispatch( new \WP_REST_Request( 'GET', '/wc/store/v1/products/reviews' ) ); + $data = $response->get_data(); + + // Assert correct response format. + $this->assertSame( 200, $response->get_status(), 'Unexpected status code.' ); + $this->assertSame( 2, count( $data ), 'Unexpected item count.' ); + + // Assert response items contain the correct properties. + $this->assertArrayHasKey( 'id', $data[0] ); + $this->assertArrayHasKey( 'date_created', $data[0] ); + $this->assertArrayHasKey( 'formatted_date_created', $data[0] ); + $this->assertArrayHasKey( 'date_created_gmt', $data[0] ); + $this->assertArrayHasKey( 'product_id', $data[0] ); + $this->assertArrayHasKey( 'product_name', $data[0] ); + $this->assertArrayHasKey( 'product_permalink', $data[0] ); + $this->assertArrayHasKey( 'product_image', $data[0] ); + $this->assertArrayHasKey( 'product_permalink', $data[0] ); + $this->assertArrayHasKey( 'reviewer', $data[0] ); + $this->assertArrayHasKey( 'review', $data[0] ); + $this->assertArrayHasKey( 'rating', $data[0] ); + $this->assertArrayHasKey( 'verified', $data[0] ); + $this->assertArrayHasKey( 'reviewer_avatar_urls', $data[0] ); + + // Assert response items contain the correct review data. + $this->assertSame( 'Test Product 2', $data[0]['product_name'] ); + $this->assertSame( 4, $data[0]['rating'] ); + $this->assertSame( 'Test Product 1', $data[1]['product_name'] ); + $this->assertSame( 5, $data[1]['rating'] ); + } + + /** + * Test getting reviews with specific order and per_page parameters. + */ + public function test_get_items_with_order_params() { + $request = new \WP_REST_Request( 'GET', '/wc/store/v1/products/reviews' ); + $request->set_param( 'per_page', 1 ); + $request->set_param( 'orderby', 'rating' ); + $request->set_param( 'order', 'desc' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'Unexpected status code.' ); + $this->assertCount( 1, $data, 'Unexpected item count.' ); + $this->assertSame( 5, $data[0]['rating'] ); + } + + /** + * Test getting reviews from a specific product. + */ + public function test_get_items_with_product_id_param() { + $request = new \WP_REST_Request( 'GET', '/wc/store/v1/products/reviews' ); + $request->set_param( 'product_id', (string) $this->products[0]->get_id() ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'Unexpected status code.' ); + $this->assertCount( 1, $data, 'Unexpected item count.' ); + $this->assertSame( 5, $data[0]['rating'] ); + } + + /** + * Test getting reviews from a specific category. + */ + public function test_get_items_with_category_id_param() { + $request = new \WP_REST_Request( 'GET', '/wc/store/v1/products/reviews' ); + $request->set_param( 'category_id', (string) $this->product_category['term_id'] ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status(), 'Unexpected status code.' ); + $this->assertCount( 1, $data, 'Unexpected item count.' ); + $this->assertSame( 4, $data[0]['rating'] ); + } +} diff --git a/plugins/woocommerce/woocommerce.php b/plugins/woocommerce/woocommerce.php index 391f2f454d9..b03085e7ac6 100644 --- a/plugins/woocommerce/woocommerce.php +++ b/plugins/woocommerce/woocommerce.php @@ -3,7 +3,7 @@ * Plugin Name: WooCommerce * Plugin URI: https://woocommerce.com/ * Description: An ecommerce toolkit that helps you sell anything. Beautifully. - * Version: 9.3.0-dev + * Version: 9.4.0-dev * Author: Automattic * Author URI: https://woocommerce.com * Text Domain: woocommerce diff --git a/tools/code-analyzer/package.json b/tools/code-analyzer/package.json index 778644ae498..83210833493 100644 --- a/tools/code-analyzer/package.json +++ b/tools/code-analyzer/package.json @@ -40,7 +40,7 @@ }, "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "config": { "ci": { diff --git a/tools/compare-perf/package.json b/tools/compare-perf/package.json index 33c901973a3..430d64146e5 100644 --- a/tools/compare-perf/package.json +++ b/tools/compare-perf/package.json @@ -19,6 +19,6 @@ }, "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" } } diff --git a/tools/monorepo-merge/package.json b/tools/monorepo-merge/package.json index b5fe6b5a8b9..3a54f6badc3 100644 --- a/tools/monorepo-merge/package.json +++ b/tools/monorepo-merge/package.json @@ -63,7 +63,7 @@ }, "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "types": "dist/index.d.ts", "config": { diff --git a/tools/monorepo-utils/package.json b/tools/monorepo-utils/package.json index 92d0e48bc61..a98b66aa8b9 100644 --- a/tools/monorepo-utils/package.json +++ b/tools/monorepo-utils/package.json @@ -67,7 +67,7 @@ }, "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "config": { "ci": { diff --git a/tools/package-release/package.json b/tools/package-release/package.json index aba30fd4937..05fc14ff4b6 100644 --- a/tools/package-release/package.json +++ b/tools/package-release/package.json @@ -58,7 +58,7 @@ }, "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "types": "dist/index.d.ts", "config": { diff --git a/tools/release-posts/package.json b/tools/release-posts/package.json index 8220abf0a27..d005269258e 100644 --- a/tools/release-posts/package.json +++ b/tools/release-posts/package.json @@ -11,7 +11,7 @@ "license": "GPL-2.0-or-later", "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "devDependencies": { "@tsconfig/node16": "^1.0.4", diff --git a/tools/storybook/package.json b/tools/storybook/package.json index 5ac43d1193e..f45ca987fa9 100644 --- a/tools/storybook/package.json +++ b/tools/storybook/package.json @@ -19,7 +19,7 @@ }, "engines": { "node": "^20.11.1", - "pnpm": "^9.1.0" + "pnpm": "9.1.3" }, "bugs": { "url": "https://github.com/woocommerce/woocommerce/issues"