diff --git a/.github/workflows/build-release-zip-file.yml b/.github/workflows/build-release-zip-file.yml index 94ae898d2a6..c6189b41c80 100644 --- a/.github/workflows/build-release-zip-file.yml +++ b/.github/workflows/build-release-zip-file.yml @@ -31,7 +31,7 @@ jobs: run: unzip plugins/woocommerce/woocommerce.zip -d zipfile - name: Upload the zip file as an artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/.github/workflows/pr-assess-performance.yml b/.github/workflows/pr-assess-performance.yml new file mode 100644 index 00000000000..b22c1719cd2 --- /dev/null +++ b/.github/workflows/pr-assess-performance.yml @@ -0,0 +1,102 @@ +name: Performance metrics + +on: + pull_request: + paths: + - 'plugins/woocommerce/composer.*' + - 'plugins/woocommerce/client/admin/config/**' + - 'plugins/woocommerce/includes/**' + - 'plugins/woocommerce/lib/**' + - 'plugins/woocommerce/patterns/**' + - 'plugins/woocommerce/src/**' + - 'plugins/woocommerce/templates/**' + - 'plugins/woocommerce/tests/metrics/**' + - 'plugins/woocommerce/.wp-env.json' + - '.github/workflows/pr-assess-performance.yml' + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} + cancel-in-progress: true + +env: + WP_ARTIFACTS_PATH: ${{ github.workspace }}/tools/compare-perf/artifacts/ + +jobs: + benchmark: + name: Evaluate performance metrics + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + name: Checkout (${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}) + with: + fetch-depth: 0 + + - uses: ./.github/actions/setup-woocommerce-monorepo + name: Install Monorepo + with: + install: '@woocommerce/plugin-woocommerce...' + build: '@woocommerce/plugin-woocommerce' + build-type: 'full' + pull-playwright-cache: true + pull-package-deps: '@woocommerce/plugin-woocommerce' + + #TODO: Inject WordPress version as per plugin requirements (relying to defaults currently). + - name: Start Test Environment + run: | + pnpm --filter="@woocommerce/plugin-woocommerce" test:e2e:install & + pnpm --filter="@woocommerce/plugin-woocommerce" env:test + + # TODO: cache results if pushed to trunk + - name: Measure performance (@${{ github.sha }}) + run: | + RESULTS_ID="editor_${{ github.sha }}_round-1" pnpm --filter="@woocommerce/plugin-woocommerce" test:metrics editor + RESULTS_ID="product-editor_${{ github.sha }}_round-1" pnpm --filter="@woocommerce/plugin-woocommerce" test:metrics product-editor + + # In alignment with .github/workflows/scripts/run-metrics.sh, we should checkout 3d7d7f02017383937f1a4158d433d0e5d44b3dc9 + # as baseline. But to avoid switching branches in 'Analyze results' step, we pick 55f855a2e6d769b5ae44305b2772eb30d3e721df + # which introduced reporting mode for the perf utility. + - name: Checkout (55f855a2e6d769b5ae44305b2772eb30d3e721df@trunk, further references as 'baseline') + run: | + git reset --hard && git checkout 55f855a2e6d769b5ae44305b2772eb30d3e721df + echo "WC_TRUNK_SHA=55f855a2e6d769b5ae44305b2772eb30d3e721df" >> $GITHUB_ENV + + # Artifacts download/upload would be more reliable, but we couldn't make it working... + - uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 + name: Cache measurements (baseline) + with: + path: tools/compare-perf/artifacts/*_${{ env.WC_TRUNK_SHA }}_* + key: ${{ runner.os }}-woocommerce-performance-measures-${{ env.WC_TRUNK_SHA }} + + - name: Verify cached measurements (baseline) + run: | + if test -n "$(find tools/compare-perf/artifacts/ -maxdepth 1 -name '*_${{ env.WC_TRUNK_SHA }}_*' -print -quit)" + then + echo "WC_MEASURE_BASELINE=no" >> $GITHUB_ENV + else + ls -l tools/compare-perf/artifacts/ + echo "Triggering baseline benchmarking" + echo "WC_MEASURE_BASELINE=yes" >> $GITHUB_ENV + fi + + - name: Build (baseline) + if: ${{ env.WC_MEASURE_BASELINE == 'yes' }} + run: | + git clean -n -d -X ./packages ./plugins | grep -v vendor | grep -v node_modules | sed -e 's/Would remove //g' | tr '\n' '\0' | xargs -0 rm -r + pnpm install --filter='@woocommerce/plugin-woocommerce...' --frozen-lockfile --config.dedupe-peer-dependents=false + pnpm --filter='@woocommerce/plugin-woocommerce' build + + #TODO: is baseline Wordpress version changes, restart environment targeting it. + + - name: Measure performance (@${{ env.WC_TRUNK_SHA }}) + if: ${{ env.WC_MEASURE_BASELINE == 'yes' }} + run: | + RESULTS_ID="editor_${{ env.WC_TRUNK_SHA }}_round-1" pnpm --filter="@woocommerce/plugin-woocommerce" test:metrics editor + RESULTS_ID="product-editor_${{ env.WC_TRUNK_SHA }}_round-1" pnpm --filter="@woocommerce/plugin-woocommerce" test:metrics product-editor + + - name: Analyze results + run: | + pnpm install --filter='compare-perf...' --frozen-lockfile --config.dedupe-peer-dependents=false + pnpm --filter="compare-perf" run compare compare-performance ${{ github.sha }} ${{ env.WC_TRUNK_SHA }} --tests-branch ${{ github.sha }} --skip-benchmarking + + # TODO: Publish to CodeVitals (see .github/workflows/scripts/run-metrics.sh) if pushed to trunk diff --git a/.github/workflows/release-code-freeze.yml b/.github/workflows/release-code-freeze.yml index 633000a1801..68a6304ee93 100644 --- a/.github/workflows/release-code-freeze.yml +++ b/.github/workflows/release-code-freeze.yml @@ -186,7 +186,7 @@ jobs: run: bash bin/build-zip.sh - name: Upload the zip file as an artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -216,7 +216,7 @@ jobs: run: bash bin/build-zip.sh - name: Upload the zip file as an artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -231,7 +231,7 @@ jobs: if: ${{ needs.code-freeze-prep.outputs.isTodayMonthlyFreeze == 'yes' }} steps: - id: download - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -279,7 +279,7 @@ jobs: working-directory: tools/monorepo-utils - id: download - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -300,7 +300,7 @@ jobs: if: ${{ needs.code-freeze-prep.outputs.isTodayAcceleratedFreeze == 'yes' }} steps: - id: download - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -348,7 +348,7 @@ jobs: working-directory: tools/monorepo-utils - id: download - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -369,7 +369,7 @@ jobs: if: ${{ needs.code-freeze-prep.outputs.isTodayMonthlyFreeze == 'yes' }} steps: - id: download - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -380,7 +380,7 @@ jobs: run: unzip ${{ steps.download.outputs.download-path }}/woocommerce.zip -d zipfile - name: Upload the zip file as an artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -395,7 +395,7 @@ jobs: if: ${{ needs.code-freeze-prep.outputs.isTodayAcceleratedFreeze == 'yes' }} steps: - id: download - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -406,7 +406,7 @@ jobs: run: unzip ${{ steps.download.outputs.download-path }}/woocommerce.zip -d zipfile - name: Upload the zip file as an artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/.github/workflows/storybook-pages.yml b/.github/workflows/storybook-pages.yml index 0e84b6df80f..1aea3190d65 100644 --- a/.github/workflows/storybook-pages.yml +++ b/.github/workflows/storybook-pages.yml @@ -1,6 +1,8 @@ name: Storybook GitHub Pages on: + schedule: + - cron: '30 2 * * *' workflow_dispatch: permissions: diff --git a/.husky/post-checkout b/.husky/post-checkout new file mode 100755 index 00000000000..1485ab1707b --- /dev/null +++ b/.husky/post-checkout @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +. "$(dirname "$0")/_/husky.sh" + +# The hook documentation: https://git-scm.com/docs/githooks.html#_post_checkout +CHECKOUT_TYPE=$3 +HEAD_NEW=$2 +HEAD_PREVIOUS=$1 + +whiteColoured='\033[0m' +orangeColoured='\033[1;33m' + +# '1' is a branch checkout +if [ "$CHECKOUT_TYPE" = '1' ]; then + # Prompt about pnpm versions mismatch when switching between branches. + currentPnpmVersion=$( ( command -v pnpm > /dev/null && pnpm -v 2>/dev/null ) || echo 'n/a' ) + targetPnpmVersion=$( grep packageManager package.json | sed -nr 's/.+packageManager.+pnpm@([[:digit:].]+).+/\1/p' ) + if [ "$currentPnpmVersion" != "$targetPnpmVersion" ]; then + printf "${orangeColoured}pnpm versions mismatch: in use '$currentPnpmVersion', needed '$targetPnpmVersion'. If you are working on something in this branch, here are some hints on how to solve this:\n" + printf "${orangeColoured}* actualize environment: 'nvm use && pnpm -v' (the most common case)\n" + printf "${orangeColoured}* install: 'npm install -g pnpm@$targetPnpmVersion'\n" + fi + + # Auto-refresh dependencies when switching between branches. + changedManifests=$( ( git diff --name-only $HEAD_NEW $HEAD_PREVIOUS | grep -E '(package.json|pnpm-lock.yaml|pnpm-workspace.yaml|composer.json|composer.lock)$' ) || echo '' ) + if [ -n "$changedManifests" ]; then + printf "${whiteColoured}The following file(s) in the new branch differs from the original one, dependencies might need to be refreshed:\n" + printf "${whiteColoured} %s\n" $changedManifests + printf "${orangeColoured}If you are working on something in this branch, ensure to refresh dependencies with 'pnpm install --frozen-lockfile'\n" + fi +fi diff --git a/.husky/post-merge b/.husky/post-merge index 7ff64bebced..bc022e8fede 100755 --- a/.husky/post-merge +++ b/.husky/post-merge @@ -1,6 +1,8 @@ #!/usr/bin/env bash . "$(dirname "$0")/_/husky.sh" +# The hook documentation: https://git-scm.com/docs/githooks.html#_post_merge + changedManifests=$( ( git diff --name-only HEAD ORIG_HEAD | grep -E '(package.json|pnpm-lock.yaml|pnpm-workspace.yaml|composer.json|composer.lock)$' ) || echo '' ) if [ -n "$changedManifests" ]; then printf "It was a change in the following file(s) - refreshing dependencies:\n" diff --git a/.markdownlint.json b/.markdownlint.json index 5e29a079a84..4a2dd1c3ec4 100644 --- a/.markdownlint.json +++ b/.markdownlint.json @@ -3,8 +3,8 @@ "MD003": { "style": "atx" }, "MD007": { "indent": 4 }, "MD013": { "line_length": 9999 }, - "MD024": { "allow_different_nesting": true }, - "MD033": { "allowed_elements": ["video"] }, + "MD024": { "siblings_only": true }, + "MD033": { "allowed_elements": [ "video" ] }, "no-hard-tabs": false, "whitespace": false } diff --git a/.npmrc b/.npmrc index 9d43c15d3cc..a140ea6b576 100644 --- a/.npmrc +++ b/.npmrc @@ -1,3 +1,5 @@ ; adding this as npm 7 automatically installs peer dependencies but pnpm does not auto-install-peers=true strict-peer-dependencies=false +; See https://github.com/pnpm/pnpm/pull/8363 (we adding the setting now, to not miss when migrating to pnpm 9.7+) +manage-package-manager-versions=true diff --git a/changelog.txt b/changelog.txt index 2ba165e2bdc..592a7f919cf 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,12 @@ == Changelog == += 9.3.2 2024-09-18 = + +- Fix - Improve the product importer's handling of filepaths under Windows [#51456](https://github.com/woocommerce/woocommerce/pull/51456) +- Fix - Revert changes related to low stock product notifications [#51441](https://github.com/woocommerce/woocommerce/pull/51441) +- Fix - Resolve a bug where manually triggering `added_to_cart` event without a button element caused an Exception [#51449](https://github.com/woocommerce/woocommerce/pull/51449) + + = 9.3.1 2024-09-12 = * Tweak - Disable remote logging feature by default [#51312](https://github.com/woocommerce/woocommerce/pull/51312) diff --git a/docs/building-a-woo-store/adding-a-custom-field-to-variable-products.md b/docs/building-a-woo-store/adding-a-custom-field-to-variable-products.md index 3b7a53c9fe8..d5cbef24096 100644 --- a/docs/building-a-woo-store/adding-a-custom-field-to-variable-products.md +++ b/docs/building-a-woo-store/adding-a-custom-field-to-variable-products.md @@ -254,4 +254,4 @@ Displaying the variation in the front store works a bit differently for variable ## How to find hooks? -Everyone will have their own preferred way, but for me, the quickest way is to look in the WooCommere plugin code. The code for each data section can be found in `/woocommerce/includes/admin/meta-boxes/views`. To view how the inventory section is handled check the `html-product-data-inventory.php` file, and for variations take a look at `html-variation-admin.php`. +Everyone will have their own preferred way, but for me, the quickest way is to look in the WooCommerce plugin code. The code for each data section can be found in `/woocommerce/includes/admin/meta-boxes/views`. To view how the inventory section is handled check the `html-product-data-inventory.php` file, and for variations take a look at `html-variation-admin.php`. diff --git a/docs/cart-and-checkout-blocks/checkout-payment-methods/payment-method-integration.md b/docs/cart-and-checkout-blocks/checkout-payment-methods/payment-method-integration.md index d3457214654..664e64370ab 100644 --- a/docs/cart-and-checkout-blocks/checkout-payment-methods/payment-method-integration.md +++ b/docs/cart-and-checkout-blocks/checkout-payment-methods/payment-method-integration.md @@ -43,12 +43,16 @@ The options you feed the configuration instance should be an object in this shap ```js const options = { name: 'my_payment_method', - content: <div>A React node</div>, - edit: <div>A React node</div>, + title: 'My Mayment Method', + description: 'A setence or two about your payment method', + gatewayId: 'gateway-id', + content: <ReactNode />, + edit: <ReactNode />, canMakePayment: () => true, paymentMethodId: 'new_payment_method', supports: { features: [], + style: [], }, }; ``` @@ -59,6 +63,18 @@ Here's some more details on the configuration options: This should be a unique string (wise to try to pick something unique for your gateway that wouldn't be used by another implementation) that is used as the identifier for the gateway client side. If `paymentMethodId` is not provided, `name` is used for `paymentMethodId` as well. +#### `title` (optional) + +This should be a human readable string with the name of your payment method. It should be sentence capitalised. It is displayed to the merchant in the editor when viewing the Checkout block to indicate which express payment methods are active. If it is not provided, the `name` will be used as the title. + +#### `description` (optional) + +This is one or two sentences maximum describing your payment gateway. It should be sentence capitalised. It is displayed to the merchant in the editor when viewing the Checkout block to indicate which express payment methods are active. + +#### `gatewayId` (optional) + +This is the ID of the Payment Gateway that your plugin registers server side, and which registers the express payment method. It is used to link your express payment method on the clinet, to a payment gateway defined on the server. It is used to direct the merchant to the right settings page within the editor. If this is not provided, the merchant will be redirected to the general Woo payment settings page. + #### `content` (required) This should be a React node that will output in the express payment method area when the block is rendered in the frontend. It will be cloned in the rendering process. When cloned, this React node will receive props passed in from the checkout payment method interface that will allow your component to interact with checkout data (more on [these props later](#props-fed-to-payment-method-nodes)). @@ -97,7 +113,11 @@ This is the only optional configuration object. The value of this property is wh This is an array of payment features supported by the gateway. It is used to crosscheck if the payment method can be used for the content of the cart. By default payment methods should support at least `products` feature. If no value is provided then this assumes that `['products']` are supported. ---- +#### `supports:style` + +This is an array of style variations supported by the express payment method. These are styles that are applied across all the active express payment buttons and can be controlled from the express payment block in the editor. Supported values for these are one of `['height', 'borderRadius']`. + +![Express Checkout Uniform Styles](https://github.com/user-attachments/assets/f0f99f3f-dca7-42b0-8685-3b098a825020) ### Payment Methods - `registerPaymentMethod( options )` @@ -139,23 +159,24 @@ The options you feed the configuration instance are the same as those for expres A big part of the payment method integration is the interface that is exposed for payment methods to use via props when the node provided is cloned and rendered on block mount. While all the props are listed below, you can find more details about what the props reference, their types etc via the [typedefs described in this file](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce-blocks/assets/js/types/type-defs/payment-method-interface.ts). -| Property | Type | Description | Values | -| ------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `activePaymentMethod` | String | The slug of the current active payment method in the checkout. | - | -| `billing` | Object | Contains everything related to billing. | `billingAddress`, `cartTotal`, `currency`, `cartTotalItems`, `displayPricesIncludingTax`, `appliedCoupons`, `customerId` | -| `cartData` | Object | Data exposed from the cart including items, fees, and any registered extension data. Note that this data should be treated as immutable (should not be modified/mutated) or it will result in errors in your application. | `cartItems`, `cartFees`, `extensions` | -| `checkoutStatus` | Object | The current checkout status exposed as various boolean state. | `isCalculating`, `isComplete`, `isIdle`, `isProcessing` | +| Property | Type | Description | Values | +| ------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `activePaymentMethod` | String | The slug of the current active payment method in the checkout. | - | +| `billing` | Object | Contains everything related to billing. | `billingAddress`, `cartTotal`, `currency`, `cartTotalItems`, `displayPricesIncludingTax`, `appliedCoupons`, `customerId` | +| `cartData` | Object | Data exposed from the cart including items, fees, and any registered extension data. Note that this data should be treated as immutable (should not be modified/mutated) or it will result in errors in your application. | `cartItems`, `cartFees`, `extensions` | +| `checkoutStatus` | Object | The current checkout status exposed as various boolean state. | `isCalculating`, `isComplete`, `isIdle`, `isProcessing` | | `components` | Object | It exposes React components that can be implemented by your payment method for various common interface elements used by payment methods. |
+ { __( + 'You currently have no express payment integrations active.', + 'woocommerce' + ) } +
+ ); + } + + return ( + <> ++ { __( + 'You currently have the following express payment integrations active.', + 'woocommerce' + ) } +
+ { Object.values( availableExpressMethods ).map( ( values ) => { + return ( ++ + +
+ + settings = apply_filters( + 'woocommerce_brands_settings_fields', + array( + array( + 'name' => __( 'Brands Archives', 'woocommerce' ), + 'type' => 'title', + 'desc' => '', + 'id' => 'brands_archives', + ), + array( + 'name' => __( 'Show description', 'woocommerce' ), + 'desc' => __( 'Choose to show the brand description on the archive page. Turn this off if you intend to use the description widget instead. Please note: this is only for themes that do not show the description.', 'woocommerce' ), + 'tip' => '', + 'id' => 'wc_brands_show_description', + 'css' => '', + 'std' => 'yes', + 'type' => 'checkbox', + ), + array( + 'type' => 'sectionend', + 'id' => 'brands_archives', + ), + ) + ); + } + + /** + * Enqueue scripts. + * + * @return void + */ + public function scripts() { + $screen = get_current_screen(); + $version = Constants::get_constant( 'WC_VERSION' ); + $suffix = Constants::is_true( 'SCRIPT_DEBUG' ) ? '' : '.min'; + + if ( 'edit-product' === $screen->id ) { + wp_register_script( + 'wc-brands-enhanced-select', + WC()->plugin_url() . '/assets/js/admin/wc-brands-enhanced-select' . $suffix . '.js', + array( 'jquery', 'selectWoo', 'wc-enhanced-select', 'wp-api' ), + $version, + true + ); + wp_localize_script( + 'wc-brands-enhanced-select', + 'wc_brands_enhanced_select_params', + array( 'ajax_url' => get_rest_url() . 'brands/search' ) + ); + wp_enqueue_script( 'wc-brands-enhanced-select' ); + } + + if ( in_array( $screen->id, array( 'edit-product_brand' ), true ) ) { + wp_enqueue_media(); + wp_enqueue_style( 'woocommerce_admin_styles' ); + } + } + + /** + * Enqueue styles. + * + * @return void + */ + public function styles() { + $version = Constants::get_constant( 'WC_VERSION' ); + wp_enqueue_style( 'brands-admin-styles', WC()->plugin_url() . '/assets/css/brands-admin.css', array(), $version ); + } + + /** + * Admin settings function. + */ + public function admin_settings() { + woocommerce_admin_fields( $this->settings ); + } + + /** + * Save admin settings function. + */ + public function save_admin_settings() { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( isset( $_GET['section'] ) && 'brands' === $_GET['section'] ) { + woocommerce_update_options( $this->settings ); + } + } + + /** + * Category thumbnails. + */ + public function add_thumbnail_field() { + global $woocommerce; + ?> +
+ + +
+ widget_cssclass = 'woocommerce widget_brand_nav widget_layered_nav'; + $this->widget_description = __( 'Shows brands in a widget which lets you narrow down the list of products when viewing products.', 'woocommerce' ); + $this->widget_id = 'woocommerce_brand_nav'; + $this->widget_name = __( 'WooCommerce Brand Layered Nav', 'woocommerce' ); + + add_filter( 'woocommerce_product_subcategories_args', array( $this, 'filter_out_cats' ) ); + + /* Create the widget. */ + parent::__construct(); + } + + /** + * Filter out all categories and not display them + * + * @param array $cat_args Category arguments. + */ + public function filter_out_cats( $cat_args ) { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( ! empty( $_GET['filter_product_brand'] ) ) { + return array( 'taxonomy' => '' ); + } + + return $cat_args; + } + + /** + * Return the currently viewed taxonomy name. + * + * @return string + */ + protected function get_current_taxonomy() { + return is_tax() ? get_queried_object()->taxonomy : ''; + } + + /** + * Return the currently viewed term ID. + * + * @return int + */ + protected function get_current_term_id() { + return absint( is_tax() ? get_queried_object()->term_id : 0 ); + } + + /** + * Return the currently viewed term slug. + * + * @return int + */ + protected function get_current_term_slug() { + return absint( is_tax() ? get_queried_object()->slug : 0 ); + } + + /** + * Widget function. + * + * @see WP_Widget + * + * @param array $args Arguments. + * @param array $instance Widget instance. + * @return void + */ + public function widget( $args, $instance ) { + $attribute_array = array(); + $attribute_taxonomies = wc_get_attribute_taxonomies(); + + if ( ! empty( $attribute_taxonomies ) ) { + foreach ( $attribute_taxonomies as $tax ) { + if ( taxonomy_exists( wc_attribute_taxonomy_name( $tax->attribute_name ) ) ) { + $attribute_array[ $tax->attribute_name ] = $tax->attribute_name; + } + } + } + + if ( ! is_post_type_archive( 'product' ) && ! is_tax( array_merge( is_array( $attribute_array ) ? $attribute_array : array(), array( 'product_cat', 'product_tag' ) ) ) ) { + return; + } + + $_chosen_attributes = WC_Query::get_layered_nav_chosen_attributes(); + + $current_term = $attribute_array && is_tax( $attribute_array ) ? get_queried_object()->term_id : ''; + $current_tax = $attribute_array && is_tax( $attribute_array ) ? get_queried_object()->taxonomy : ''; + + /** + * Filter the widget's title. + * + * @since 9.4.0 + * + * @param string $title Widget title + * @param array $instance The settings for the particular instance of the widget. + * @param string $woo_widget_idbase The widget's id base. + */ + $title = apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base ); + $taxonomy = 'product_brand'; + $display_type = isset( $instance['display_type'] ) ? $instance['display_type'] : 'list'; + + if ( ! taxonomy_exists( $taxonomy ) ) { + return; + } + + // Get only parent terms. Methods will recursively retrieve children. + $terms = get_terms( + array( + 'taxonomy' => $taxonomy, + 'hide_empty' => true, + 'parent' => 0, + ) + ); + + if ( empty( $terms ) ) { + return; + } + + ob_start(); + + $this->widget_start( $args, $instance ); + + if ( 'dropdown' === $display_type ) { + $found = $this->layered_nav_dropdown( $terms, $taxonomy ); + } else { + $found = $this->layered_nav_list( $terms, $taxonomy ); + } + + $this->widget_end( $args ); + + // Force found when option is selected - do not force found on taxonomy attributes. + if ( ! is_tax() && is_array( $_chosen_attributes ) && array_key_exists( $taxonomy, $_chosen_attributes ) ) { + $found = true; + } + + if ( ! $found ) { + ob_end_clean(); + } else { + echo ob_get_clean(); // phpcs:ignore WordPress.Security.EscapeOutput + } + } + + /** + * Update function. + * + * @see WP_Widget->update + * + * @param array $new_instance The new settings for the particular instance of the widget. + * @param array $old_instance The old settings for the particular instance of the widget. + * @return array + */ + public function update( $new_instance, $old_instance ) { + global $woocommerce; + + if ( empty( $new_instance['title'] ) ) { + $new_instance['title'] = __( 'Brands', 'woocommerce' ); + } + + $instance['title'] = wp_strip_all_tags( stripslashes( $new_instance['title'] ) ); + $instance['display_type'] = stripslashes( $new_instance['display_type'] ); + + return $instance; + } + + /** + * Form function. + * + * @see WP_Widget->form + * + * @param array $instance Widget instance. + * @return void + */ + public function form( $instance ) { + global $woocommerce; + + if ( ! isset( $instance['display_type'] ) ) { + $instance['display_type'] = 'list'; + } + ?> ++ +
+ ++
+ $data ) { + if ( $name === $taxonomy ) { + continue; + } + $filter_name = sanitize_title( str_replace( 'pa_', '', $name ) ); + if ( ! empty( $data['terms'] ) ) { + $link = add_query_arg( 'filter_' . $filter_name, implode( ',', $data['terms'] ), $link ); + } + if ( 'or' === $data['query_type'] ) { + $link = add_query_arg( 'query_type_' . $filter_name, 'or', $link ); + } + } + } + + // phpcs:enable WordPress.Security.NonceVerification.Recommended + return esc_url( $link ); + } + + /** + * Gets the currently selected attributes + * + * @return array + */ + public function get_chosen_attributes() { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( ! empty( $_GET['filter_product_brand'] ) ) { + $filter_product_brand = wc_clean( wp_unslash( $_GET['filter_product_brand'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended + return array_map( 'intval', explode( ',', $filter_product_brand ) ); + } + + return array(); + } + + /** + * Show dropdown layered nav. + * + * @param array $terms Terms. + * @param string $taxonomy Taxonomy. + * @param int $depth Depth. + * @return bool Will nav display? + */ + protected function layered_nav_dropdown( $terms, $taxonomy, $depth = 0 ) { + $found = false; + + if ( $taxonomy !== $this->get_current_taxonomy() ) { + $term_counts = $this->get_filtered_term_product_counts( wp_list_pluck( $terms, 'term_id' ), $taxonomy, 'or' ); + $_chosen_attributes = $this->get_chosen_attributes(); + + if ( 0 === $depth ) { + echo ''; + + wc_enqueue_js( + " + jQuery( '.wc-brand-dropdown-layered-nav-" . esc_js( $taxonomy ) . "' ).change( function() { + var slug = jQuery( this ).val(); + location.href = '" . preg_replace( '%\/page\/[0-9]+%', '', str_replace( array( '&', '%2C' ), array( '&', ',' ), esc_js( add_query_arg( 'filtering', '1', $link ) ) ) ) . '&filter_' . esc_js( $taxonomy ) . "=' + jQuery( '.wc-brand-dropdown-layered-nav-" . esc_js( $taxonomy ) . "' ).val(); + }); + " + ); + } + } + + return $found; + } + + /** + * Show list based layered nav. + * + * @param array $terms Terms. + * @param string $taxonomy Taxonomy. + * @param int $depth Depth. + * @return bool Will nav display? + */ + protected function layered_nav_list( $terms, $taxonomy, $depth = 0 ) { + // List display. + echo '+ + +
+ ++ + +
+ ++ + id="get_field_id( 'fluid_columns' ) ); ?>" name="get_field_name( 'fluid_columns' ) ); ?>" /> +
+ ++ + +
+ ++ + +
+ ++ + +
+ ++ + +
+ get_param( 'redirect_url' ); - $calypso_env = defined( 'WOOCOMMERCE_CALYPSO_ENVIRONMENT' ) && in_array( WOOCOMMERCE_CALYPSO_ENVIRONMENT, [ 'development', 'wpcalypso', 'horizon', 'stage' ], true ) ? WOOCOMMERCE_CALYPSO_ENVIRONMENT : 'production'; + $calypso_env = defined( 'WOOCOMMERCE_CALYPSO_ENVIRONMENT' ) && in_array( WOOCOMMERCE_CALYPSO_ENVIRONMENT, array( 'development', 'wpcalypso', 'horizon', 'stage' ), true ) ? WOOCOMMERCE_CALYPSO_ENVIRONMENT : 'production'; + + $authorization_url = $manager->get_authorization_url( null, $redirect_url ); + $authorization_url = add_query_arg( 'locale', $this->get_wpcom_locale(), $authorization_url ); + + if ( Features::is_enabled( 'use-wp-horizon' ) ) { + $calypso_env = 'horizon'; + } return [ 'success' => ! $errors->has_errors(), 'errors' => $errors->get_error_messages(), 'url' => add_query_arg( - [ + array( 'from' => $request->get_param( 'from' ), 'calypso_env' => $calypso_env, - ], - $manager->get_authorization_url( null, $redirect_url ) + ), + $authorization_url, ), ]; } + /** + * Return a locale string for wpcom. + * + * @return string + */ + private function get_wpcom_locale() { + // List of locales that should be used with region code. + $locale_to_lang = array( + 'bre' => 'br', + 'de_AT' => 'de-at', + 'de_CH' => 'de-ch', + 'de' => 'de_formal', + 'el' => 'el-po', + 'en_GB' => 'en-gb', + 'es_CL' => 'es-cl', + 'es_MX' => 'es-mx', + 'fr_BE' => 'fr-be', + 'fr_CA' => 'fr-ca', + 'nl_BE' => 'nl-be', + 'nl' => 'nl_formal', + 'pt_BR' => 'pt-br', + 'sr' => 'sr_latin', + 'zh_CN' => 'zh-cn', + 'zh_HK' => 'zh-hk', + 'zh_SG' => 'zh-sg', + 'zh_TW' => 'zh-tw', + ); + + $system_locale = get_locale(); + if ( isset( $locale_to_lang[ $system_locale ] ) ) { + // Return the locale with region code if it's in the list. + return $locale_to_lang[ $system_locale ]; + } + + // If the locale is not in the list, return the language code only. + return explode( '_', $system_locale )[0]; + } + /** * Check whether the current user has permission to install plugins * @@ -400,7 +446,7 @@ class OnboardingPlugins extends WC_REST_Data_Controller { ), $slug ), - 'type' => 'plugin_info_api_error', + 'type' => 'plugin_info_api_error', 'slug' => $slug, 'api_version' => $api->version, 'api_download_link' => $api->download_link, diff --git a/plugins/woocommerce/src/Admin/API/Reports/Customers/DataStore.php b/plugins/woocommerce/src/Admin/API/Reports/Customers/DataStore.php index e13ed41c47c..7f740eff74e 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Customers/DataStore.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Customers/DataStore.php @@ -615,6 +615,11 @@ class DataStore extends ReportsDataStore implements DataStoreInterface { if ( is_null( $customer_user ) ) { $customer_user = new \WC_Customer( $user_id ); } + + // Set email as customer email instead of Order Billing Email if we have a customer. + $data['email'] = $customer_user->get_email( 'edit' ); + + // Adding other relevant customer data. $data['user_id'] = $user_id; $data['username'] = $customer_user->get_username( 'edit' ); $data['date_registered'] = $customer_user->get_date_created( 'edit' ) ? $customer_user->get_date_created( 'edit' )->date( TimeInterval::$sql_datetime_format ) : null; diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Tax.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Tax.php index ba33d9b2362..b0a65b969e6 100644 --- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Tax.php +++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Tax.php @@ -15,6 +15,7 @@ class Tax extends Task { /** * Used to cache is_complete() method result. + * * @var null */ private $is_complete_result = null; @@ -109,12 +110,16 @@ class Tax extends Task { */ public function is_complete() { if ( $this->is_complete_result === null ) { - $wc_connect_taxes_enabled = get_option( 'wc_connect_taxes_enabled' ); + $wc_connect_taxes_enabled = get_option( 'wc_connect_taxes_enabled' ); $is_wc_connect_taxes_enabled = ( $wc_connect_taxes_enabled === 'yes' ) || ( $wc_connect_taxes_enabled === true ); // seems that in some places boolean is used, and other places 'yes' | 'no' is used + // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment -- We will replace this with a formal system by WC 9.6 so lets not advertise it yet. + $third_party_complete = apply_filters( 'woocommerce_admin_third_party_tax_setup_complete', false ); + $this->is_complete_result = $is_wc_connect_taxes_enabled || count( TaxDataStore::get_taxes( array() ) ) > 0 || - get_option( 'woocommerce_no_sales_tax' ) !== false; + get_option( 'woocommerce_no_sales_tax' ) !== false || + $third_party_complete; } return $this->is_complete_result; diff --git a/plugins/woocommerce/src/Admin/WCAdminHelper.php b/plugins/woocommerce/src/Admin/WCAdminHelper.php index 9e234762eb8..768102e35fb 100644 --- a/plugins/woocommerce/src/Admin/WCAdminHelper.php +++ b/plugins/woocommerce/src/Admin/WCAdminHelper.php @@ -154,11 +154,13 @@ class WCAdminHelper { 'post_type' => 'product', ); - parse_str( wp_parse_url( $url, PHP_URL_QUERY ), $url_params ); - - foreach ( $params as $key => $param ) { - if ( isset( $url_params[ $key ] ) && $url_params[ $key ] === $param ) { - return true; + $query_string = wp_parse_url( $url, PHP_URL_QUERY ); + if ( $query_string ) { + parse_str( $query_string, $url_params ); + foreach ( $params as $key => $param ) { + if ( isset( $url_params[ $key ] ) && $url_params[ $key ] === $param ) { + return true; + } } } diff --git a/plugins/woocommerce/src/Blocks/AIContent/UpdateProducts.php b/plugins/woocommerce/src/Blocks/AIContent/UpdateProducts.php index 571cb08029e..3ad62a779bb 100644 --- a/plugins/woocommerce/src/Blocks/AIContent/UpdateProducts.php +++ b/plugins/woocommerce/src/Blocks/AIContent/UpdateProducts.php @@ -4,6 +4,7 @@ namespace Automattic\WooCommerce\Blocks\AIContent; use Automattic\WooCommerce\Blocks\AI\Connection; use WP_Error; + /** * Pattern Images class. * @@ -473,11 +474,11 @@ class UpdateProducts { /** * Update the product with the new content. * - * @param \WC_Product $product The product. - * @param int $product_image_id The product image ID. - * @param string $product_title The product title. - * @param string $product_description The product description. - * @param int $product_price The product price. + * @param \WC_Product $product The product. + * @param int|string|WP_Error $product_image_id The product image ID. + * @param string $product_title The product title. + * @param string $product_description The product description. + * @param int $product_price The product price. * * @return int|\WP_Error */ diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/Breadcrumbs.php b/plugins/woocommerce/src/Blocks/BlockTypes/Breadcrumbs.php index dc25d2258bd..fb0e0b1cc0e 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/Breadcrumbs.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/Breadcrumbs.php @@ -34,14 +34,16 @@ class Breadcrumbs extends AbstractBlock { return; } - $classname = $attributes['className'] ?? ''; $classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes ); return sprintf( - ' ', - esc_attr( $classes_and_styles['classes'] ), - esc_attr( $classname ), - esc_attr( $classes_and_styles['styles'] ), + '';
printf(
- /* translators: %s: is referring to the plugin's name. */
+ /* translators: %s: is referring to the plugin's name. */
esc_html__( 'The %1$s plugin has been deactivated as the latest improvements are now included with the %2$s plugin.', 'woocommerce' ),
'' . esc_html( $plugin_data['Name'] ) . '
',
'WooCommerce
'
@@ -118,13 +194,71 @@ class Packages {
}
}
+ /**
+ * Prevent plugins already merged into WooCommerce core from getting activated as standalone plugins.
+ *
+ * @param string $plugin Plugin name.
+ */
+ public static function deactivate_merged_plugins( $plugin ) {
+ $plugin_dir = basename( dirname( $plugin ) );
+
+ if ( self::is_package_enabled( $plugin_dir ) ) {
+ $plugins_url = esc_url( admin_url( 'plugins.php' ) );
+ wp_die(
+ esc_html__( 'This plugin cannot be activated because its functionality is now included in WooCommerce core.', 'woocommerce' ),
+ esc_html__( 'Plugin Activation Error', 'woocommerce' ),
+ array(
+ 'link_url' => esc_url( $plugins_url ),
+ 'link_text' => esc_html__( 'Return to the Plugins page', 'woocommerce' ),
+ ),
+ );
+ }
+ }
+
+ /**
+ * Mark merged plugins as pending update.
+ * This is required for correctly displaying maintenance notices.
+ *
+ * @param array $plugins Plugins list.
+ */
+ public static function mark_merged_plugins_as_pending_update( $plugins ) {
+ foreach ( $plugins as $plugin_name => $plugin_data ) {
+ $plugin_dir = basename( dirname( $plugin_name ) );
+ if ( self::is_package_enabled( $plugin_dir ) ) {
+ // Necessary to properly display notice within row.
+ $plugins[ $plugin_name ]['update'] = 1;
+ }
+ }
+ return $plugins;
+ }
+
+ /**
+ * Displays a maintenance notice next to merged plugins, to inform users
+ * that the plugin functionality is now offered by WooCommerce core.
+ *
+ * Requires 'mark_merged_plugins_as_pending_update' to properly display this notice.
+ *
+ * @param string $plugin_file Plugin file.
+ */
+ public static function display_notice_for_merged_plugins( $plugin_file ) {
+ global $wp_list_table;
+
+ $plugin_dir = basename( dirname( $plugin_file ) );
+ $columns_count = $wp_list_table->get_column_count();
+ $notice = __( 'This plugin can no longer be activated because its functionality is now included in WooCommerce. It is recommended to delete it.', 'woocommerce' );
+
+ if ( self::is_package_enabled( $plugin_dir ) ) {
+ echo '
diff --git a/plugins/woocommerce/templates/brands/brand-description.php b/plugins/woocommerce/templates/brands/brand-description.php new file mode 100644 index 00000000000..a72a251a3f6 --- /dev/null +++ b/plugins/woocommerce/templates/brands/brand-description.php @@ -0,0 +1,35 @@ + +
Review content here
\n", - 'rating' => 0, - 'verified' => false, - 'reviewer_avatar_urls' => $product_reviews[0]['reviewer_avatar_urls'], - '_links' => array( - 'self' => array( - array( - 'href' => rest_url( '/wc/v3/products/reviews/' . $review_id ), + $this->assertEmpty( + ArrayUtil::deep_assoc_array_diff( + array( + 'id' => $review_id, + 'date_created' => $product_reviews[0]['date_created'], + 'date_created_gmt' => $product_reviews[0]['date_created_gmt'], + 'product_id' => $product->get_id(), + 'product_name' => $product->get_name(), + 'product_permalink' => $product->get_permalink(), + 'status' => 'approved', + 'reviewer' => 'admin', + 'reviewer_email' => 'woo@woo.local', + 'review' => "Review content here
\n", + 'rating' => 0, + 'verified' => false, + 'reviewer_avatar_urls' => $product_reviews[0]['reviewer_avatar_urls'], + '_links' => array( + 'self' => array( + array( + 'href' => rest_url( '/wc/v3/products/reviews/' . $review_id ), + ), ), - ), - 'collection' => array( - array( - 'href' => rest_url( '/wc/v3/products/reviews' ), + 'collection' => array( + array( + 'href' => rest_url( '/wc/v3/products/reviews' ), + ), ), - ), - 'up' => array( - array( - 'href' => rest_url( '/wc/v3/products/' . $product->get_id() ), + 'up' => array( + array( + 'href' => rest_url( '/wc/v3/products/' . $product->get_id() ), + ), ), ), ), - ), - $product_reviews + $product_reviews[0] + ) ); } diff --git a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version2/settings.php b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version2/settings.php index 32a96fb95dc..f185a811097 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version2/settings.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version2/settings.php @@ -6,6 +6,7 @@ * @since 3.0.0 */ +use Automattic\WooCommerce\Utilities\ArrayUtil; use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts; /** @@ -482,29 +483,39 @@ class Settings_V2 extends WC_REST_Unit_Test_Case { $response = $this->server->dispatch( new WP_REST_Request( 'GET', '/wc/v2/settings/products' ) ); $data = $response->get_data(); $this->assertTrue( is_array( $data ) ); - $this->assertContains( - array( - 'id' => 'woocommerce_downloads_require_login', - 'label' => 'Access restriction', - 'description' => 'Downloads require login', - 'type' => 'checkbox', - 'default' => 'no', - 'tip' => 'This setting does not apply to guest purchases.', - 'value' => 'no', - '_links' => array( - 'self' => array( - array( - 'href' => rest_url( '/wc/v2/settings/products/woocommerce_downloads_require_login' ), + $data_download_required_login = null; + foreach ( $data as $setting ) { + if ( 'woocommerce_downloads_require_login' === $setting['id'] ) { + $data_download_required_login = $setting; + break; + } + } + $this->assertNotEmpty( $data_download_required_login ); + $this->assertEmpty( + ArrayUtil::deep_assoc_array_diff( + array( + 'id' => 'woocommerce_downloads_require_login', + 'label' => 'Access restriction', + 'description' => 'Downloads require login', + 'type' => 'checkbox', + 'default' => 'no', + 'tip' => 'This setting does not apply to guest purchases.', + 'value' => 'no', + '_links' => array( + 'self' => array( + array( + 'href' => rest_url( '/wc/v2/settings/products/woocommerce_downloads_require_login' ), + ), ), - ), - 'collection' => array( - array( - 'href' => rest_url( '/wc/v2/settings/products' ), + 'collection' => array( + array( + 'href' => rest_url( '/wc/v2/settings/products' ), + ), ), ), ), - ), - $data + $data_download_required_login + ) ); // test get single. @@ -540,29 +551,41 @@ class Settings_V2 extends WC_REST_Unit_Test_Case { $this->assertEquals( 200, $response->get_status() ); - $this->assertContains( - array( - 'id' => 'recipient', - 'label' => 'Recipient(s)', - 'description' => 'Enter recipients (comma separated) for this email. Defaults toadmin@example.org
.',
- 'type' => 'text',
- 'default' => '',
- 'tip' => 'Enter recipients (comma separated) for this email. Defaults to admin@example.org
.',
- 'value' => '',
- '_links' => array(
- 'self' => array(
- array(
- 'href' => rest_url( '/wc/v2/settings/email_new_order/recipient' ),
+ $recipient_setting = null;
+ foreach ( $settings as $setting ) {
+ if ( 'recipient' === $setting['id'] ) {
+ $recipient_setting = $setting;
+ break;
+ }
+ }
+
+ $this->assertNotEmpty( $recipient_setting );
+
+ $this->assertEmpty(
+ ArrayUtil::deep_assoc_array_diff(
+ array(
+ 'id' => 'recipient',
+ 'label' => 'Recipient(s)',
+ 'description' => 'Enter recipients (comma separated) for this email. Defaults to admin@example.org
.',
+ 'type' => 'text',
+ 'default' => '',
+ 'tip' => 'Enter recipients (comma separated) for this email. Defaults to admin@example.org
.',
+ 'value' => '',
+ '_links' => array(
+ 'self' => array(
+ array(
+ 'href' => rest_url( '/wc/v2/settings/email_new_order/recipient' ),
+ ),
),
- ),
- 'collection' => array(
- array(
- 'href' => rest_url( '/wc/v2/settings/email_new_order' ),
+ 'collection' => array(
+ array(
+ 'href' => rest_url( '/wc/v2/settings/email_new_order' ),
+ ),
),
),
),
- ),
- $settings
+ $recipient_setting
+ )
);
// test get single.
diff --git a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version2/shipping-methods.php b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version2/shipping-methods.php
index 3f13ecb2ee1..d27efd5b67b 100644
--- a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version2/shipping-methods.php
+++ b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version2/shipping-methods.php
@@ -1,4 +1,7 @@
get_data();
$this->assertEquals( 200, $response->get_status() );
- $this->assertContains(
- array(
- 'id' => 'free_shipping',
- 'title' => 'Free shipping',
- 'description' => 'Free shipping is a special method which can be triggered with coupons and minimum spends.',
- '_links' => array(
- 'self' => array(
- array(
- 'href' => rest_url( '/wc/v2/shipping_methods/free_shipping' ),
+
+ $free_shipping_method = null;
+ foreach ( $methods as $method ) {
+ if ( 'free_shipping' === $method['id'] ) {
+ $free_shipping_method = $method;
+ break;
+ }
+ }
+ $this->assertNotEmpty( $free_shipping_method );
+
+ $this->assertEmpty(
+ ArrayUtil::deep_assoc_array_diff(
+ array(
+ 'id' => 'free_shipping',
+ 'title' => 'Free shipping',
+ 'description' => 'Free shipping is a special method which can be triggered with coupons and minimum spends.',
+ '_links' => array(
+ 'self' => array(
+ array(
+ 'href' => rest_url( '/wc/v2/shipping_methods/free_shipping' ),
+ ),
),
- ),
- 'collection' => array(
- array(
- 'href' => rest_url( '/wc/v2/shipping_methods' ),
+ 'collection' => array(
+ array(
+ 'href' => rest_url( '/wc/v2/shipping_methods' ),
+ ),
),
),
),
- ),
- $methods
+ $free_shipping_method
+ )
);
}
diff --git a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version2/shipping-zones.php b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version2/shipping-zones.php
index bc020791d02..1e15bb91e02 100644
--- a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version2/shipping-zones.php
+++ b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version2/shipping-zones.php
@@ -1,5 +1,7 @@
assertEquals( 200, $response->get_status() );
$this->assertEquals( count( $data ), 1 );
- $this->assertContains(
- array(
- 'id' => $data[0]['id'],
- 'name' => 'Locations not covered by your other zones',
- 'order' => 0,
- '_links' => array(
- 'self' => array(
- array(
- 'href' => rest_url( '/wc/v2/shipping/zones/' . $data[0]['id'] ),
+ $this->assertEmpty(
+ ArrayUtil::deep_assoc_array_diff(
+ array(
+ 'id' => $data[0]['id'],
+ 'name' => 'Locations not covered by your other zones',
+ 'order' => 0,
+ '_links' => array(
+ 'self' => array(
+ array(
+ 'href' => rest_url( '/wc/v2/shipping/zones/' . $data[0]['id'] ),
+ ),
),
- ),
- 'collection' => array(
- array(
- 'href' => rest_url( '/wc/v2/shipping/zones' ),
+ 'collection' => array(
+ array(
+ 'href' => rest_url( '/wc/v2/shipping/zones' ),
+ ),
),
- ),
- 'describedby' => array(
- array(
- 'href' => rest_url( '/wc/v2/shipping/zones/' . $data[0]['id'] . '/locations' ),
+ 'describedby' => array(
+ array(
+ 'href' => rest_url( '/wc/v2/shipping/zones/' . $data[0]['id'] . '/locations' ),
+ ),
),
),
),
- ),
- $data
+ $data[0]
+ )
);
// Create a zone and make sure it's in the response
@@ -108,30 +112,32 @@ class WC_Tests_API_Shipping_Zones_V2 extends WC_REST_Unit_Test_Case {
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( count( $data ), 2 );
- $this->assertContains(
- array(
- 'id' => $data[1]['id'],
- 'name' => 'Zone 1',
- 'order' => 0,
- '_links' => array(
- 'self' => array(
- array(
- 'href' => rest_url( '/wc/v2/shipping/zones/' . $data[1]['id'] ),
+ $this->assertEmpty(
+ ArrayUtil::deep_assoc_array_diff(
+ array(
+ 'id' => $data[1]['id'],
+ 'name' => 'Zone 1',
+ 'order' => 0,
+ '_links' => array(
+ 'self' => array(
+ array(
+ 'href' => rest_url( '/wc/v2/shipping/zones/' . $data[1]['id'] ),
+ ),
),
- ),
- 'collection' => array(
- array(
- 'href' => rest_url( '/wc/v2/shipping/zones' ),
+ 'collection' => array(
+ array(
+ 'href' => rest_url( '/wc/v2/shipping/zones' ),
+ ),
),
- ),
- 'describedby' => array(
- array(
- 'href' => rest_url( '/wc/v2/shipping/zones/' . $data[1]['id'] . '/locations' ),
+ 'describedby' => array(
+ array(
+ 'href' => rest_url( '/wc/v2/shipping/zones/' . $data[1]['id'] . '/locations' ),
+ ),
),
),
),
- ),
- $data
+ $data[1]
+ )
);
}
@@ -195,30 +201,32 @@ class WC_Tests_API_Shipping_Zones_V2 extends WC_REST_Unit_Test_Case {
$data = $response->get_data();
$this->assertEquals( 201, $response->get_status() );
- $this->assertEquals(
- array(
- 'id' => $data['id'],
- 'name' => 'Test Zone',
- 'order' => 1,
- '_links' => array(
- 'self' => array(
- array(
- 'href' => rest_url( '/wc/v2/shipping/zones/' . $data['id'] ),
+ $this->assertEmpty(
+ ArrayUtil::deep_assoc_array_diff(
+ array(
+ 'id' => $data['id'],
+ 'name' => 'Test Zone',
+ 'order' => 1,
+ '_links' => array(
+ 'self' => array(
+ array(
+ 'href' => rest_url( '/wc/v2/shipping/zones/' . $data['id'] ),
+ ),
),
- ),
- 'collection' => array(
- array(
- 'href' => rest_url( '/wc/v2/shipping/zones' ),
+ 'collection' => array(
+ array(
+ 'href' => rest_url( '/wc/v2/shipping/zones' ),
+ ),
),
- ),
- 'describedby' => array(
- array(
- 'href' => rest_url( '/wc/v2/shipping/zones/' . $data['id'] . '/locations' ),
+ 'describedby' => array(
+ array(
+ 'href' => rest_url( '/wc/v2/shipping/zones/' . $data['id'] . '/locations' ),
+ ),
),
),
),
- ),
- $data
+ $data
+ )
);
}
@@ -260,30 +268,32 @@ class WC_Tests_API_Shipping_Zones_V2 extends WC_REST_Unit_Test_Case {
$data = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
- $this->assertEquals(
- array(
- 'id' => $zone->get_id(),
- 'name' => 'Zone Test',
- 'order' => 2,
- '_links' => array(
- 'self' => array(
- array(
- 'href' => rest_url( '/wc/v2/shipping/zones/' . $zone->get_id() ),
+ $this->assertEmpty(
+ ArrayUtil::deep_assoc_array_diff(
+ array(
+ 'id' => $zone->get_id(),
+ 'name' => 'Zone Test',
+ 'order' => 2,
+ '_links' => array(
+ 'self' => array(
+ array(
+ 'href' => rest_url( '/wc/v2/shipping/zones/' . $zone->get_id() ),
+ ),
),
- ),
- 'collection' => array(
- array(
- 'href' => rest_url( '/wc/v2/shipping/zones' ),
+ 'collection' => array(
+ array(
+ 'href' => rest_url( '/wc/v2/shipping/zones' ),
+ ),
),
- ),
- 'describedby' => array(
- array(
- 'href' => rest_url( '/wc/v2/shipping/zones/' . $zone->get_id() . '/locations' ),
+ 'describedby' => array(
+ array(
+ 'href' => rest_url( '/wc/v2/shipping/zones/' . $zone->get_id() . '/locations' ),
+ ),
),
),
),
- ),
- $data
+ $data
+ )
);
}
@@ -359,30 +369,32 @@ class WC_Tests_API_Shipping_Zones_V2 extends WC_REST_Unit_Test_Case {
$data = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
- $this->assertEquals(
- array(
- 'id' => $zone->get_id(),
- 'name' => 'Test Zone',
- 'order' => 0,
- '_links' => array(
- 'self' => array(
- array(
- 'href' => rest_url( '/wc/v2/shipping/zones/' . $zone->get_id() ),
+ $this->assertEmpty(
+ ArrayUtil::deep_assoc_array_diff(
+ array(
+ 'id' => $zone->get_id(),
+ 'name' => 'Test Zone',
+ 'order' => 0,
+ '_links' => array(
+ 'self' => array(
+ array(
+ 'href' => rest_url( '/wc/v2/shipping/zones/' . $zone->get_id() ),
+ ),
),
- ),
- 'collection' => array(
- array(
- 'href' => rest_url( '/wc/v2/shipping/zones' ),
+ 'collection' => array(
+ array(
+ 'href' => rest_url( '/wc/v2/shipping/zones' ),
+ ),
),
- ),
- 'describedby' => array(
- array(
- 'href' => rest_url( '/wc/v2/shipping/zones/' . $zone->get_id() . '/locations' ),
+ 'describedby' => array(
+ array(
+ 'href' => rest_url( '/wc/v2/shipping/zones/' . $zone->get_id() . '/locations' ),
+ ),
),
),
),
- ),
- $data
+ $data
+ )
);
}
@@ -624,13 +636,13 @@ class WC_Tests_API_Shipping_Zones_V2 extends WC_REST_Unit_Test_Case {
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( count( $data ), 1 );
- $this->assertContains( $expected, $data );
+ $this->assertEmpty( ArrayUtil::deep_assoc_array_diff( $expected, $data[0] ) );
$response = $this->server->dispatch( new WP_REST_Request( 'GET', '/wc/v2/shipping/zones/' . $zone->get_id() . '/methods/' . $instance_id ) );
$data = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
- $this->assertEquals( $expected, $data );
+ $this->assertEmpty( ArrayUtil::deep_assoc_array_diff( $expected, $data ) );
}
/**
diff --git a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/product-reviews.php b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/product-reviews.php
index 11aa94c16b7..b655ffb538a 100644
--- a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/product-reviews.php
+++ b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/product-reviews.php
@@ -1,4 +1,7 @@
assertEquals( 200, $response->get_status() );
$this->assertEquals( 10, count( $product_reviews ) );
- $this->assertContains(
- array(
- 'id' => $review_id,
- 'date_created' => $product_reviews[0]['date_created'],
- 'date_created_gmt' => $product_reviews[0]['date_created_gmt'],
- 'product_id' => $product->get_id(),
- 'product_name' => $product->get_name(),
- 'product_permalink' => $product->get_permalink(),
- 'status' => 'approved',
- 'reviewer' => 'admin',
- 'reviewer_email' => 'woo@woo.local',
- 'review' => "Review content here
\n", - 'rating' => 0, - 'verified' => false, - 'reviewer_avatar_urls' => $product_reviews[0]['reviewer_avatar_urls'], - '_links' => array( - 'self' => array( - array( - 'href' => rest_url( '/wc/v3/products/reviews/' . $review_id ), + $this->assertEmpty( + ArrayUtil::deep_assoc_array_diff( + array( + 'id' => $review_id, + 'date_created' => $product_reviews[0]['date_created'], + 'date_created_gmt' => $product_reviews[0]['date_created_gmt'], + 'product_id' => $product->get_id(), + 'product_name' => $product->get_name(), + 'product_permalink' => $product->get_permalink(), + 'status' => 'approved', + 'reviewer' => 'admin', + 'reviewer_email' => 'woo@woo.local', + 'review' => "Review content here
\n", + 'rating' => 0, + 'verified' => false, + 'reviewer_avatar_urls' => $product_reviews[0]['reviewer_avatar_urls'], + '_links' => array( + 'self' => array( + array( + 'href' => rest_url( '/wc/v3/products/reviews/' . $review_id ), + ), ), - ), - 'collection' => array( - array( - 'href' => rest_url( '/wc/v3/products/reviews' ), + 'collection' => array( + array( + 'href' => rest_url( '/wc/v3/products/reviews' ), + ), ), - ), - 'up' => array( - array( - 'href' => rest_url( '/wc/v3/products/' . $product->get_id() ), + 'up' => array( + array( + 'href' => rest_url( '/wc/v3/products/' . $product->get_id() ), + ), ), ), ), - ), - $product_reviews + $product_reviews[0] + ) ); } diff --git a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/settings.php b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/settings.php index d8890569eff..d10062ec2ee 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/settings.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/settings.php @@ -6,6 +6,7 @@ * @since 3.5.0 */ +use Automattic\WooCommerce\Utilities\ArrayUtil; use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts; /** @@ -481,29 +482,42 @@ class Settings extends WC_REST_Unit_Test_Case { $response = $this->server->dispatch( new WP_REST_Request( 'GET', '/wc/v3/settings/products' ) ); $data = $response->get_data(); $this->assertTrue( is_array( $data ) ); - $this->assertContains( - array( - 'id' => 'woocommerce_downloads_require_login', - 'label' => 'Access restriction', - 'description' => 'Downloads require login', - 'type' => 'checkbox', - 'default' => 'no', - 'tip' => 'This setting does not apply to guest purchases.', - 'value' => 'no', - '_links' => array( - 'self' => array( - array( - 'href' => rest_url( '/wc/v3/settings/products/woocommerce_downloads_require_login' ), + + $setting_downloads_required = null; + foreach ( $data as $setting ) { + if ( 'woocommerce_downloads_require_login' === $setting['id'] ) { + $setting_downloads_required = $setting; + break; + } + } + + $this->assertNotEmpty( $setting_downloads_required ); + + $this->assertEmpty( + ArrayUtil::deep_assoc_array_diff( + array( + 'id' => 'woocommerce_downloads_require_login', + 'label' => 'Access restriction', + 'description' => 'Downloads require login', + 'type' => 'checkbox', + 'default' => 'no', + 'tip' => 'This setting does not apply to guest purchases.', + 'value' => 'no', + '_links' => array( + 'self' => array( + array( + 'href' => rest_url( '/wc/v3/settings/products/woocommerce_downloads_require_login' ), + ), ), - ), - 'collection' => array( - array( - 'href' => rest_url( '/wc/v3/settings/products' ), + 'collection' => array( + array( + 'href' => rest_url( '/wc/v3/settings/products' ), + ), ), ), ), - ), - $data + $setting_downloads_required + ) ); // test get single. @@ -539,29 +553,41 @@ class Settings extends WC_REST_Unit_Test_Case { $this->assertEquals( 200, $response->get_status() ); - $this->assertContains( - array( - 'id' => 'recipient', - 'label' => 'Recipient(s)', - 'description' => 'Enter recipients (comma separated) for this email. Defaults toadmin@example.org
.',
- 'type' => 'text',
- 'default' => '',
- 'tip' => 'Enter recipients (comma separated) for this email. Defaults to admin@example.org
.',
- 'value' => '',
- '_links' => array(
- 'self' => array(
- array(
- 'href' => rest_url( '/wc/v3/settings/email_new_order/recipient' ),
+ $recipient_setting = null;
+ foreach ( $settings as $setting ) {
+ if ( 'recipient' === $setting['id'] ) {
+ $recipient_setting = $setting;
+ break;
+ }
+ }
+
+ $this->assertNotEmpty( $recipient_setting );
+
+ $this->assertEmpty(
+ ArrayUtil::deep_assoc_array_diff(
+ array(
+ 'id' => 'recipient',
+ 'label' => 'Recipient(s)',
+ 'description' => 'Enter recipients (comma separated) for this email. Defaults to admin@example.org
.',
+ 'type' => 'text',
+ 'default' => '',
+ 'tip' => 'Enter recipients (comma separated) for this email. Defaults to admin@example.org
.',
+ 'value' => '',
+ '_links' => array(
+ 'self' => array(
+ array(
+ 'href' => rest_url( '/wc/v3/settings/email_new_order/recipient' ),
+ ),
),
- ),
- 'collection' => array(
- array(
- 'href' => rest_url( '/wc/v3/settings/email_new_order' ),
+ 'collection' => array(
+ array(
+ 'href' => rest_url( '/wc/v3/settings/email_new_order' ),
+ ),
),
),
),
- ),
- $settings
+ $recipient_setting
+ )
);
// test get single.
diff --git a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/shipping-methods.php b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/shipping-methods.php
index 31dc36c1b14..05d38ad0517 100644
--- a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/shipping-methods.php
+++ b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/shipping-methods.php
@@ -1,4 +1,7 @@
get_data();
$this->assertEquals( 200, $response->get_status() );
- $this->assertContains(
- array(
- 'id' => 'free_shipping',
- 'title' => 'Free shipping',
- 'description' => 'Free shipping is a special method which can be triggered with coupons and minimum spends.',
- '_links' => array(
- 'self' => array(
- array(
- 'href' => rest_url( '/wc/v3/shipping_methods/free_shipping' ),
+
+ $free_shipping = null;
+ foreach ( $methods as $method ) {
+ if ( 'free_shipping' === $method['id'] ) {
+ $free_shipping = $method;
+ break;
+ }
+ }
+ $this->assertNotEmpty( $free_shipping );
+
+ $this->assertEmpty(
+ ArrayUtil::deep_assoc_array_diff(
+ array(
+ 'id' => 'free_shipping',
+ 'title' => 'Free shipping',
+ 'description' => 'Free shipping is a special method which can be triggered with coupons and minimum spends.',
+ '_links' => array(
+ 'self' => array(
+ array(
+ 'href' => rest_url( '/wc/v3/shipping_methods/free_shipping' ),
+ ),
),
- ),
- 'collection' => array(
- array(
- 'href' => rest_url( '/wc/v3/shipping_methods' ),
+ 'collection' => array(
+ array(
+ 'href' => rest_url( '/wc/v3/shipping_methods' ),
+ ),
),
),
),
- ),
- $methods
+ $free_shipping
+ )
);
}
diff --git a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/shipping-zones.php b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/shipping-zones.php
index 1dd58034653..3c49902d989 100644
--- a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/shipping-zones.php
+++ b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/shipping-zones.php
@@ -1,5 +1,7 @@
assertEquals( 200, $response->get_status() );
$this->assertEquals( count( $data ), 1 );
- $this->assertContains(
- array(
- 'id' => $data[0]['id'],
- 'name' => 'Locations not covered by your other zones',
- 'order' => 0,
- '_links' => array(
- 'self' => array(
- array(
- 'href' => rest_url( '/wc/v3/shipping/zones/' . $data[0]['id'] ),
+ $this->assertEmpty(
+ ArrayUtil::deep_assoc_array_diff(
+ array(
+ 'id' => $data[0]['id'],
+ 'name' => 'Locations not covered by your other zones',
+ 'order' => 0,
+ '_links' => array(
+ 'self' => array(
+ array(
+ 'href' => rest_url( '/wc/v3/shipping/zones/' . $data[0]['id'] ),
+ ),
),
- ),
- 'collection' => array(
- array(
- 'href' => rest_url( '/wc/v3/shipping/zones' ),
+ 'collection' => array(
+ array(
+ 'href' => rest_url( '/wc/v3/shipping/zones' ),
+ ),
),
- ),
- 'describedby' => array(
- array(
- 'href' => rest_url( '/wc/v3/shipping/zones/' . $data[0]['id'] . '/locations' ),
+ 'describedby' => array(
+ array(
+ 'href' => rest_url( '/wc/v3/shipping/zones/' . $data[0]['id'] . '/locations' ),
+ ),
),
),
),
- ),
- $data
+ $data[0]
+ )
);
// Create a zone and make sure it's in the response
@@ -111,30 +115,32 @@ class WC_Tests_API_Shipping_Zones extends WC_REST_Unit_Test_Case {
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( count( $data ), 2 );
- $this->assertContains(
- array(
- 'id' => $data[1]['id'],
- 'name' => 'Zone 1',
- 'order' => 0,
- '_links' => array(
- 'self' => array(
- array(
- 'href' => rest_url( '/wc/v3/shipping/zones/' . $data[1]['id'] ),
+ $this->assertEmpty(
+ ArrayUtil::deep_assoc_array_diff(
+ array(
+ 'id' => $data[1]['id'],
+ 'name' => 'Zone 1',
+ 'order' => 0,
+ '_links' => array(
+ 'self' => array(
+ array(
+ 'href' => rest_url( '/wc/v3/shipping/zones/' . $data[1]['id'] ),
+ ),
),
- ),
- 'collection' => array(
- array(
- 'href' => rest_url( '/wc/v3/shipping/zones' ),
+ 'collection' => array(
+ array(
+ 'href' => rest_url( '/wc/v3/shipping/zones' ),
+ ),
),
- ),
- 'describedby' => array(
- array(
- 'href' => rest_url( '/wc/v3/shipping/zones/' . $data[1]['id'] . '/locations' ),
+ 'describedby' => array(
+ array(
+ 'href' => rest_url( '/wc/v3/shipping/zones/' . $data[1]['id'] . '/locations' ),
+ ),
),
),
),
- ),
- $data
+ $data[1]
+ )
);
}
@@ -202,30 +208,32 @@ class WC_Tests_API_Shipping_Zones extends WC_REST_Unit_Test_Case {
$data = $response->get_data();
$this->assertEquals( 201, $response->get_status() );
- $this->assertEquals(
- array(
- 'id' => $data['id'],
- 'name' => 'Test Zone',
- 'order' => 1,
- '_links' => array(
- 'self' => array(
- array(
- 'href' => rest_url( '/wc/v3/shipping/zones/' . $data['id'] ),
+ $this->assertEmpty(
+ ArrayUtil::deep_assoc_array_diff(
+ array(
+ 'id' => $data['id'],
+ 'name' => 'Test Zone',
+ 'order' => 1,
+ '_links' => array(
+ 'self' => array(
+ array(
+ 'href' => rest_url( '/wc/v3/shipping/zones/' . $data['id'] ),
+ ),
),
- ),
- 'collection' => array(
- array(
- 'href' => rest_url( '/wc/v3/shipping/zones' ),
+ 'collection' => array(
+ array(
+ 'href' => rest_url( '/wc/v3/shipping/zones' ),
+ ),
),
- ),
- 'describedby' => array(
- array(
- 'href' => rest_url( '/wc/v3/shipping/zones/' . $data['id'] . '/locations' ),
+ 'describedby' => array(
+ array(
+ 'href' => rest_url( '/wc/v3/shipping/zones/' . $data['id'] . '/locations' ),
+ ),
),
),
),
- ),
- $data
+ $data
+ )
);
}
@@ -269,30 +277,32 @@ class WC_Tests_API_Shipping_Zones extends WC_REST_Unit_Test_Case {
$data = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
- $this->assertEquals(
- array(
- 'id' => $zone->get_id(),
- 'name' => 'Zone Test',
- 'order' => 2,
- '_links' => array(
- 'self' => array(
- array(
- 'href' => rest_url( '/wc/v3/shipping/zones/' . $zone->get_id() ),
+ $this->assertEmpty(
+ ArrayUtil::deep_assoc_array_diff(
+ array(
+ 'id' => $zone->get_id(),
+ 'name' => 'Zone Test',
+ 'order' => 2,
+ '_links' => array(
+ 'self' => array(
+ array(
+ 'href' => rest_url( '/wc/v3/shipping/zones/' . $zone->get_id() ),
+ ),
),
- ),
- 'collection' => array(
- array(
- 'href' => rest_url( '/wc/v3/shipping/zones' ),
+ 'collection' => array(
+ array(
+ 'href' => rest_url( '/wc/v3/shipping/zones' ),
+ ),
),
- ),
- 'describedby' => array(
- array(
- 'href' => rest_url( '/wc/v3/shipping/zones/' . $zone->get_id() . '/locations' ),
+ 'describedby' => array(
+ array(
+ 'href' => rest_url( '/wc/v3/shipping/zones/' . $zone->get_id() . '/locations' ),
+ ),
),
),
),
- ),
- $data
+ $data
+ )
);
}
@@ -373,30 +383,32 @@ class WC_Tests_API_Shipping_Zones extends WC_REST_Unit_Test_Case {
$data = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
- $this->assertEquals(
- array(
- 'id' => $zone->get_id(),
- 'name' => 'Test Zone',
- 'order' => 0,
- '_links' => array(
- 'self' => array(
- array(
- 'href' => rest_url( '/wc/v3/shipping/zones/' . $zone->get_id() ),
+ $this->assertEmpty(
+ ArrayUtil::deep_assoc_array_diff(
+ array(
+ 'id' => $zone->get_id(),
+ 'name' => 'Test Zone',
+ 'order' => 0,
+ '_links' => array(
+ 'self' => array(
+ array(
+ 'href' => rest_url( '/wc/v3/shipping/zones/' . $zone->get_id() ),
+ ),
),
- ),
- 'collection' => array(
- array(
- 'href' => rest_url( '/wc/v3/shipping/zones' ),
+ 'collection' => array(
+ array(
+ 'href' => rest_url( '/wc/v3/shipping/zones' ),
+ ),
),
- ),
- 'describedby' => array(
- array(
- 'href' => rest_url( '/wc/v3/shipping/zones/' . $zone->get_id() . '/locations' ),
+ 'describedby' => array(
+ array(
+ 'href' => rest_url( '/wc/v3/shipping/zones/' . $zone->get_id() . '/locations' ),
+ ),
),
),
),
- ),
- $data
+ $data
+ )
);
}
@@ -644,13 +656,12 @@ class WC_Tests_API_Shipping_Zones extends WC_REST_Unit_Test_Case {
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( count( $data ), 1 );
- $this->assertContains( $expected, $data );
-
+ $this->assertEmpty( ArrayUtil::deep_assoc_array_diff( $expected, $data[0] ) );
$response = $this->server->dispatch( new WP_REST_Request( 'GET', '/wc/v3/shipping/zones/' . $zone->get_id() . '/methods/' . $instance_id ) );
$data = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
- $this->assertEquals( $expected, $data );
+ $this->assertEmpty( ArrayUtil::deep_assoc_array_diff( $expected, $data ) );
}
/**
diff --git a/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-brands-test.php b/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-brands-test.php
new file mode 100644
index 00000000000..5ca5953daf5
--- /dev/null
+++ b/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-brands-test.php
@@ -0,0 +1,116 @@
+factory()->term->create(
+ array(
+ 'taxonomy' => 'product_brand',
+ 'name' => 'Blah_A',
+ )
+ );
+ $term_b_id = $this->factory()->term->create(
+ array(
+ 'taxonomy' => 'product_brand',
+ 'name' => 'Foo_A',
+ )
+ );
+ $term_c_id = $this->factory()->term->create(
+ array(
+ 'taxonomy' => 'product_brand',
+ 'name' => 'Blah_B',
+ )
+ );
+
+ wp_set_post_terms( $simple_product->get_id(), array( $term_a_id, $term_b_id, $term_c_id ), 'product_brand' );
+
+ add_filter(
+ 'woocommerce_product_brand_filter_threshold',
+ function () {
+ return 3;
+ }
+ );
+
+ $brands_admin = new WC_Brands_Admin();
+ ob_start();
+ $brands_admin->render_product_brand_filter();
+ $output = ob_get_contents();
+ ob_end_clean();
+
+ $this->assertStringContainsString(
+ '