Merge branch 'trunk' into try/add-settings-refresh-next
This commit is contained in:
commit
98782a5f4b
|
@ -0,0 +1,7 @@
|
|||
# Report Flaky Tests
|
||||
|
||||
A GitHub action to report flaky E2E tests to GitHub issues.
|
||||
|
||||
**This package is still experimental and breaking changes could be introduced in future minor versions (`v0.x`). Use it at your own risks.**
|
||||
|
||||
<br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p>
|
|
@ -0,0 +1,17 @@
|
|||
name: 'Report flaky tests'
|
||||
description: 'Report flaky tests to GitHub issues'
|
||||
inputs:
|
||||
repo-token:
|
||||
description: 'GitHub token'
|
||||
required: true
|
||||
label:
|
||||
description: 'The flaky-test label name'
|
||||
required: true
|
||||
default: 'flaky-test'
|
||||
artifact-path:
|
||||
description: 'The path of the downloaded artifact'
|
||||
required: true
|
||||
default: 'flaky-tests'
|
||||
runs:
|
||||
using: 'node20'
|
||||
main: 'dist/index.js'
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,41 @@
|
|||
/*!
|
||||
* fill-range <https://github.com/jonschlinkert/fill-range>
|
||||
*
|
||||
* Copyright (c) 2014-present, Jon Schlinkert.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* is-number <https://github.com/jonschlinkert/is-number>
|
||||
*
|
||||
* Copyright (c) 2014-present, Jon Schlinkert.
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* is-plain-object <https://github.com/jonschlinkert/is-plain-object>
|
||||
*
|
||||
* Copyright (c) 2014-2017, Jon Schlinkert.
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* to-regex-range <https://github.com/micromatch/to-regex-range>
|
||||
*
|
||||
* Copyright (c) 2015-present, Jon Schlinkert.
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
|
||||
/*! formdata-polyfill. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
|
||||
|
||||
/*! ws. MIT License. Einar Otto Stangvik <einaros@gmail.com> */
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-is.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
|
@ -16,6 +16,9 @@ inputs:
|
|||
pull-playwright-cache:
|
||||
description: 'Given a boolean value, invokes Playwright dependencies caching.'
|
||||
default: false
|
||||
pull-package-deps:
|
||||
description: 'Given a string value, will pull the package specific dependencies cache.'
|
||||
default: false
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
|
@ -31,27 +34,46 @@ runs:
|
|||
uses: 'actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65'
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
# We only want to use the cache if something is being installed.
|
||||
cache: ${{ inputs.install != 'false' && 'pnpm' || '' }}
|
||||
# The built-in caching is not fit to per-package caching we are aiming.
|
||||
cache: ''
|
||||
- name: 'Setup PHP'
|
||||
if: ${{ inputs.php-version != 'false' }}
|
||||
uses: 'shivammathur/setup-php@a36e1e52ff4a1c9e9c9be31551ee4712a6cb6bd0'
|
||||
with:
|
||||
php-version: '${{ inputs.php-version }}'
|
||||
coverage: 'none'
|
||||
- name: 'Cache: identify pnpm caching directory'
|
||||
if: ${{ inputs.pull-package-deps != 'false' }}
|
||||
shell: 'bash'
|
||||
run: |
|
||||
echo "PNPM_STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
- name: 'Cache: pnpm downloads'
|
||||
if: ${{ inputs.pull-package-deps != 'false' }}
|
||||
uses: 'actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319'
|
||||
with:
|
||||
path: "${{ env.PNPM_STORE_PATH }}"
|
||||
key: "${{ runner.os }}-pnpm-${{ inputs.pull-package-deps }}-build:${{ inputs.build-type }}-${{ hashFiles( 'pnpm-lock.yaml' ) }}"
|
||||
restore-keys: '${{ runner.os }}-pnpm-${{ inputs.pull-package-deps }}-build:${{ inputs.build-type }}-'
|
||||
- name: 'Cache: node cache'
|
||||
if: ${{ inputs.pull-package-deps != 'false' }}
|
||||
uses: 'actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319'
|
||||
with:
|
||||
path: './node_modules/.cache'
|
||||
key: "${{ runner.os }}-node-cache-${{ inputs.pull-package-deps }}-${{ hashFiles( 'pnpm-lock.yaml' ) }}"
|
||||
restore-keys: '${{ runner.os }}-node-cache-${{ inputs.pull-package-deps }}-'
|
||||
- name: 'Cache Composer Dependencies'
|
||||
if: ${{ inputs.build == 'false' }}
|
||||
if: ${{ inputs.pull-package-deps != 'false' }}
|
||||
uses: 'actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319'
|
||||
with:
|
||||
path: '~/.cache/composer/files'
|
||||
key: "${{ runner.os }}-composer-${{ hashFiles( '**/composer.lock' ) }}"
|
||||
restore-keys: '${{ runner.os }}-composer-'
|
||||
key: "${{ runner.os }}-composer-${{ inputs.pull-package-deps }}-${{ hashFiles( 'packages/*/*/composer.lock', 'plugins/*/composer.lock' ) }}"
|
||||
restore-keys: '${{ runner.os }}-composer-${{ inputs.pull-package-deps }}-'
|
||||
- name: 'Cache: playwright downloads'
|
||||
if: ${{ inputs.pull-playwright-cache != 'false' }}
|
||||
uses: 'actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319'
|
||||
with:
|
||||
path: '~/.cache/ms-playwright/'
|
||||
key: "${{ runner.os }}-playwright-${{ hashFiles( '**/pnpm-lock.yaml' ) }}"
|
||||
key: "${{ runner.os }}-playwright-${{ hashFiles( 'pnpm-lock.yaml' ) }}"
|
||||
restore-keys: '${{ runner.os }}-playwright-'
|
||||
- name: 'Parse Project Filters'
|
||||
id: 'project-filters'
|
||||
|
@ -63,9 +85,16 @@ runs:
|
|||
# Boolean inputs aren't parsed into filters so it'll either be "true" or there will be a filter.
|
||||
if: ${{ inputs.install == 'true' || steps.project-filters.outputs.install != '' }}
|
||||
shell: 'bash'
|
||||
run: 'pnpm install'
|
||||
# `pnpm install` filtering is broken: https://github.com/pnpm/pnpm/issues/6300
|
||||
# run: 'pnpm install ${{ steps.project-filters.outputs.install }}'
|
||||
# The installation command is a bit odd as it's a workaround for know bug - https://github.com/pnpm/pnpm/issues/6300.
|
||||
run: |
|
||||
if [[ '${{ inputs.install }}' == '@woocommerce/plugin-woocommerce...' && '${{ inputs.build-type }}' == 'backend' ]]; then
|
||||
# PHPUnit/REST testing optimized installation of the deps: minimalistic and parallellized between PHP/JS.
|
||||
# JS deps installation is abit hard-core, but all we need actually is wp-env and playwright - we are good at that regard.
|
||||
composer install --working-dir=./plugins/woocommerce --quiet &
|
||||
pnpm install --filter='@woocommerce/plugin-woocommerce' --frozen-lockfile --config.dedupe-peer-dependents=false --ignore-scripts
|
||||
else
|
||||
pnpm install ${{ steps.project-filters.outputs.install }} --frozen-lockfile ${{ steps.project-filters.outputs.install != '' && '--config.dedupe-peer-dependents=false' || '' }}
|
||||
fi
|
||||
# We want to include an option to build projects using this action so that we can make
|
||||
# sure that the build cache is always used when building projects.
|
||||
- name: 'Cache Build Output'
|
||||
|
@ -75,6 +104,8 @@ runs:
|
|||
- name: 'Build'
|
||||
# Boolean inputs aren't parsed into filters so it'll either be "true" or there will be a filter.
|
||||
if: ${{ inputs.build == 'true' || steps.project-filters.outputs.build != '' }}
|
||||
env:
|
||||
BROWSERSLIST_IGNORE_OLD_DATA: true
|
||||
shell: 'bash'
|
||||
run: |
|
||||
if [[ '${{ inputs.build-type }}' == 'backend' ]]; then
|
||||
|
|
|
@ -12,4 +12,4 @@ jobs:
|
|||
uses: acq688/Request-Reviewer-For-Team-Action@v1.1
|
||||
with:
|
||||
config: '.github/automate-team-review-assignment-config.yml'
|
||||
GITHUB_TOKEN: ${{ secrets.FINE_GRAINED_TOKEN_ACTIONS }}
|
||||
GITHUB_TOKEN: ${{ secrets.PR_ASSIGN_TOKEN }}
|
||||
|
|
|
@ -30,12 +30,8 @@ jobs:
|
|||
|
||||
- name: Setup WooCommerce Monorepo
|
||||
uses: ./.github/actions/setup-woocommerce-monorepo
|
||||
|
||||
- name: Cache PNPM Dependencies
|
||||
uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
cache: pnpm
|
||||
pull-package-deps: '@woocommerce/plugin-woocommerce'
|
||||
|
||||
- name: Prepare plugin zips
|
||||
id: prepare
|
||||
|
|
|
@ -20,6 +20,8 @@ jobs:
|
|||
|
||||
- name: Setup WooCommerce Monorepo
|
||||
uses: ./.github/actions/setup-woocommerce-monorepo
|
||||
with:
|
||||
pull-package-deps: '@woocommerce/plugin-woocommerce'
|
||||
|
||||
- name: Build zip
|
||||
working-directory: plugins/woocommerce
|
||||
|
|
|
@ -14,48 +14,15 @@ on:
|
|||
required: true
|
||||
default: 'default'
|
||||
type: string
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr_simulate:
|
||||
description: 'Would you like to run CI on a pull request? If so, enter the PR number here. If blank, the entire suite will be run.'
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
concurrency:
|
||||
group: '${{ github.workflow }}-${{ github.ref }}'
|
||||
group: '${{ github.workflow }}-${{ github.ref }}-${{ inputs.trigger }}'
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
dispatch-handler:
|
||||
name: 'Handle dispatched workflow'
|
||||
runs-on: 'ubuntu-20.04'
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && inputs.pr_simulate }}
|
||||
outputs:
|
||||
head: ${{ steps.pr-info.outputs.head }}
|
||||
base: ${{ steps.pr-info.outputs.base }}
|
||||
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
name: 'Grab PR info.'
|
||||
id: 'pr-info'
|
||||
env:
|
||||
PR: ${{ inputs.pr_simulate }}
|
||||
with:
|
||||
retries: 3
|
||||
script: |
|
||||
if ( ! process.env.PR ) {
|
||||
return;
|
||||
}
|
||||
const PR = await github.rest.pulls.get( {
|
||||
pull_number: process.env.PR,
|
||||
repo: context.repo.repo,
|
||||
owner: context.repo.owner,
|
||||
} );
|
||||
core.setOutput( 'head', PR.data.head.ref );
|
||||
core.setOutput( 'base', PR.data.base.ref );
|
||||
project-jobs:
|
||||
# Since this is a monorepo, not every pull request or change is going to impact every project.
|
||||
# Instead of running CI tasks on all projects indiscriminately, we use a command to detect
|
||||
|
@ -63,12 +30,6 @@ jobs:
|
|||
# matrices that we can use to run CI tasks only on the projects that need them.
|
||||
name: 'Build Project Jobs'
|
||||
runs-on: 'ubuntu-20.04'
|
||||
needs: 'dispatch-handler'
|
||||
# Because forks of this repository may want to skip running this CI automatically, but still
|
||||
# be able to run it via workflow_dispatch, if the SKIP_CI variable is truthy, and we're not
|
||||
# running from a workflow_dispatch, we'll skip generating the project matrix and any jobs.
|
||||
# Because dispatch-handler may be skipped, we need the always() here.
|
||||
if: ${{ always() && ( github.event_name == 'workflow_dispatch' || ! vars.SKIP_CI ) }}
|
||||
outputs:
|
||||
lint-jobs: ${{ steps.project-jobs.outputs.lint-jobs }}
|
||||
test-jobs: ${{ steps.project-jobs.outputs.test-jobs }}
|
||||
|
@ -78,24 +39,19 @@ jobs:
|
|||
name: 'Checkout'
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# If the workflow wasn't triggered by dispatch, this will be empty and use defaults.
|
||||
ref: ${{ needs.dispatch-handler.outputs.head }}
|
||||
|
||||
- uses: './.github/actions/setup-woocommerce-monorepo'
|
||||
name: 'Setup Monorepo'
|
||||
with:
|
||||
php-version: false # We don't want to waste time installing PHP since we aren't using it in this job.
|
||||
|
||||
- uses: actions/github-script@v7
|
||||
name: 'Build Matrix'
|
||||
id: 'project-jobs'
|
||||
env:
|
||||
PR_SIM: ${{ needs.dispatch-handler.outputs.base }}
|
||||
with:
|
||||
script: |
|
||||
const prSim = process.env.PR_SIM;
|
||||
|
||||
let baseRef = prSim || ${{ toJson( github.base_ref ) }};
|
||||
// Intended behaviour of the jobs generation:
|
||||
// - PRs: run CI jobs aiming PRs and filter out jobs based on the content changes
|
||||
// - Pushes: run CI jobs aiming pushes without filtering based on the content changes
|
||||
let baseRef = ${{ toJson( github.base_ref ) }};
|
||||
if ( baseRef ) {
|
||||
baseRef = `--base-ref origin/${ baseRef }`;
|
||||
}
|
||||
|
@ -118,65 +74,48 @@ jobs:
|
|||
githubEvent = trigger;
|
||||
}
|
||||
|
||||
// Override the event 'workflow_dispatch' event type if we're simulating a PR.
|
||||
if ( prSim ) {
|
||||
githubEvent = 'pull_request';
|
||||
}
|
||||
|
||||
const child_process = require( 'node:child_process' );
|
||||
child_process.execSync( `pnpm utils ci-jobs ${ baseRef } --event ${ githubEvent }` );
|
||||
|
||||
|
||||
project-lint-jobs:
|
||||
name: "Lint - ${{ matrix.projectName }} ${{ matrix.optional && ' (optional)' || ''}}"
|
||||
runs-on: 'ubuntu-20.04'
|
||||
needs: [
|
||||
'project-jobs',
|
||||
'dispatch-handler'
|
||||
]
|
||||
# Because dispatch-handler may be skipped, we need the always() here.
|
||||
if: ${{ always() && needs.project-jobs.outputs.lint-jobs != '[]' && ( github.event_name == 'pull_request' || inputs.pr_simulate != '' ) }}
|
||||
needs: 'project-jobs'
|
||||
if: ${{ needs.project-jobs.outputs.lint-jobs != '[]' && github.event_name == 'pull_request' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include: ${{ fromJSON( needs.project-jobs.outputs.lint-jobs ) }}
|
||||
|
||||
steps:
|
||||
- uses: 'actions/checkout@v4'
|
||||
name: 'Checkout'
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# If the workflow wasn't triggered by dispatch, this will be empty and use defaults.
|
||||
ref: ${{ needs.dispatch-handler.outputs.head }}
|
||||
# the WooCommerce plugin package uses phpcs-changed for linting, which requires non-shallow git-history.
|
||||
fetch-depth: ${{ ( ( matrix.projectName == '@woocommerce/plugin-woocommerce' && '0' ) || '1' ) }}
|
||||
|
||||
- uses: './.github/actions/setup-woocommerce-monorepo'
|
||||
name: 'Setup Monorepo'
|
||||
id: 'setup-monorepo'
|
||||
with:
|
||||
install: '${{ matrix.projectName }}...'
|
||||
pull-package-deps: '${{ matrix.projectName }}'
|
||||
|
||||
- name: 'Lint'
|
||||
run: 'pnpm --filter="${{ matrix.projectName }}" ${{ matrix.command }}'
|
||||
|
||||
|
||||
project-test-jobs:
|
||||
name: "${{ matrix.name }}"
|
||||
runs-on: 'ubuntu-20.04'
|
||||
needs: [
|
||||
'project-jobs',
|
||||
'dispatch-handler'
|
||||
]
|
||||
if: ${{ always() && needs.project-jobs.outputs.test-jobs != '[]' }}
|
||||
needs: 'project-jobs'
|
||||
if: ${{ needs.project-jobs.outputs.test-jobs != '[]' }}
|
||||
env: ${{ matrix.testEnv.envVars }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include: ${{ fromJSON( needs.project-jobs.outputs.test-jobs ) }}
|
||||
|
||||
steps:
|
||||
- uses: 'actions/checkout@v4'
|
||||
name: 'Checkout'
|
||||
with:
|
||||
# If the workflow wasn't triggered by dispatch, this will be empty and use defaults.
|
||||
ref: ${{ needs.dispatch-handler.outputs.head }}
|
||||
|
||||
- uses: './.github/actions/setup-woocommerce-monorepo'
|
||||
name: 'Install Monorepo'
|
||||
|
@ -186,14 +125,16 @@ jobs:
|
|||
build: ${{ ( github.ref_type == 'tag' && 'false' ) || matrix.projectName }}
|
||||
build-type: ${{ ( matrix.testType == 'unit:php' && 'backend' ) || 'full' }}
|
||||
pull-playwright-cache: ${{ matrix.testEnv.shouldCreate && matrix.testType == 'e2e' }}
|
||||
pull-package-deps: '${{ matrix.projectName }}'
|
||||
|
||||
- name: 'Update wp-env config'
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
env:
|
||||
RELEASE_TAG: ${{ github.ref_name }}
|
||||
ARTIFACT_NAME: ${{ github.ref_name == 'nightly' && 'woocommerce-trunk-nightly.zip' || 'woocommerce.zip' }}
|
||||
working-directory: ${{ matrix.projectPath }}
|
||||
run: node ./tests/e2e-pw/bin/override-wp-env-plugins.js
|
||||
# band-aid to get the path to wp-env.json for blocks e2e tests, until they're migrated to plugins/woocommerce
|
||||
WP_ENV_CONFIG_PATH: ${{ github.workspace }}/${{ matrix.testEnv.start == 'env:start:blocks' && 'plugins/woocommerce-blocks' || matrix.projectPath }}
|
||||
run: node .github/workflows/scripts/override-wp-env-plugins.js
|
||||
|
||||
- name: 'Start Test Environment'
|
||||
id: 'prepare-test-environment'
|
||||
|
@ -270,7 +211,7 @@ jobs:
|
|||
'project-lint-jobs',
|
||||
'project-test-jobs',
|
||||
]
|
||||
if: ${{ always() && github.event_name == 'pull_request' }}
|
||||
if: ${{ !cancelled() && github.event_name == 'pull_request' }}
|
||||
steps:
|
||||
- uses: 'actions/checkout@v4'
|
||||
name: 'Checkout'
|
||||
|
@ -299,7 +240,7 @@ jobs:
|
|||
'project-lint-jobs',
|
||||
'project-test-jobs',
|
||||
]
|
||||
if: ${{ always() && github.event_name != 'pull_request' && ! github.event.pull_request.head.repo.fork }}
|
||||
if: ${{ !cancelled() && github.event_name != 'pull_request' && github.repository == 'woocommerce/woocommerce' }}
|
||||
steps:
|
||||
- uses: 'actions/checkout@v4'
|
||||
name: 'Checkout'
|
||||
|
@ -336,7 +277,7 @@ jobs:
|
|||
'project-jobs',
|
||||
'project-test-jobs',
|
||||
]
|
||||
if: ${{ always() && needs.project-jobs.outputs.report-jobs != '[]' && ! github.event.pull_request.head.repo.fork }}
|
||||
if: ${{ !cancelled() && needs.project-jobs.outputs.report-jobs != '[]' && github.repository == 'woocommerce/woocommerce' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
@ -390,48 +331,42 @@ jobs:
|
|||
-f suite="$REPORT_NAME" \
|
||||
-f report_title="$REPORT_TITLE" \
|
||||
--repo woocommerce/woocommerce-test-reports
|
||||
|
||||
|
||||
report-flaky-tests:
|
||||
name: 'Create issues for flaky tests'
|
||||
if: ${{ !cancelled() && ! github.event.pull_request.head.repo.fork }}
|
||||
needs: [ 'project-test-jobs' ]
|
||||
if: ${{ !cancelled() && github.repository == 'woocommerce/woocommerce' && needs.project-jobs.outputs.test-jobs != '[]' }}
|
||||
needs:
|
||||
[
|
||||
'project-jobs',
|
||||
'project-test-jobs',
|
||||
]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: WordPress/gutenberg
|
||||
ref: dbf201449e9736f672b61e422787d47659db327a
|
||||
- uses: 'actions/checkout@v4'
|
||||
name: 'Checkout'
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
id: download-artifact
|
||||
- uses: 'actions/download-artifact@v4'
|
||||
name: 'Download artifacts'
|
||||
with:
|
||||
pattern: flaky-tests*
|
||||
path: flaky-tests
|
||||
merge-multiple: true
|
||||
|
||||
- name: 'Check if there are flaky tests reports'
|
||||
- name: 'Merge flaky tests reports'
|
||||
run: |
|
||||
downloadPath='${{ steps.download-artifact.outputs.download-path }}'
|
||||
downloadPath='${{ steps.download-artifact.outputs.download-path || './flaky-tests' }}'
|
||||
# make dir so that next step doesn't fail if it doesn't exist
|
||||
mkdir -p $downloadPath
|
||||
# any output means there are reports
|
||||
echo "FLAKY_REPORTS=$(ls -A $downloadPath | head -1)" >> $GITHUB_ENV
|
||||
|
||||
- name: 'Setup'
|
||||
if: ${{ !!env.FLAKY_REPORTS }}
|
||||
uses: ./.github/setup-node
|
||||
|
||||
- name: 'Build packages'
|
||||
if: ${{ !!env.FLAKY_REPORTS }}
|
||||
run: npm run build:packages
|
||||
|
||||
- name: 'Report flaky tests'
|
||||
if: ${{ !!env.FLAKY_REPORTS }}
|
||||
uses: ./packages/report-flaky-tests
|
||||
uses: './.github/actions/report-flaky-tests'
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
label: 'metric: flaky e2e test'
|
||||
|
|
|
@ -52,6 +52,7 @@ jobs:
|
|||
install: '@woocommerce/plugin-woocommerce...'
|
||||
build: '@woocommerce/plugin-woocommerce'
|
||||
pull-playwright-cache: true
|
||||
pull-package-deps: '@woocommerce/plugin-woocommerce'
|
||||
|
||||
- name: Install Playwright dependencies
|
||||
run: pnpm exec playwright install chromium --with-deps
|
||||
|
|
|
@ -18,6 +18,8 @@ jobs:
|
|||
|
||||
- name: Setup WooCommerce Monorepo
|
||||
uses: ./.github/actions/setup-woocommerce-monorepo
|
||||
with:
|
||||
pull-package-deps: '@woocommerce/plugin-woocommerce'
|
||||
|
||||
- name: Build zip
|
||||
working-directory: plugins/woocommerce
|
||||
|
|
|
@ -24,6 +24,8 @@ jobs:
|
|||
|
||||
- name: Setup WooCommerce Monorepo
|
||||
uses: ./.github/actions/setup-woocommerce-monorepo
|
||||
with:
|
||||
pull-package-deps: '@woocommerce/plugin-woocommerce'
|
||||
|
||||
- name: Build zip
|
||||
working-directory: plugins/woocommerce
|
||||
|
|
|
@ -27,6 +27,7 @@ jobs:
|
|||
with:
|
||||
install: true
|
||||
build: './tools/package-release'
|
||||
pull-package-deps: 'tools/package-release'
|
||||
|
||||
- name: Clean working directory
|
||||
run: git checkout pnpm-lock.yaml # in case for whatever reason the lockfile is out of sync, there won't be interference with npm publish.
|
||||
|
|
|
@ -44,10 +44,16 @@ jobs:
|
|||
# Both install and build are handled by compressed-size-action.
|
||||
install: false
|
||||
build: false
|
||||
pull-package-deps: '@woocommerce/plugin-woocommerce'
|
||||
|
||||
- uses: preactjs/compressed-size-action@f780fd104362cfce9e118f9198df2ee37d12946c
|
||||
env:
|
||||
BROWSERSLIST_IGNORE_OLD_DATA: true
|
||||
with:
|
||||
repo-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
pattern: './{packages/js/!(*e2e*|*internal*|*test*|*plugin*|*create*),plugins/woocommerce-blocks}/{build,build-style}/**/*.{js,css}'
|
||||
clean-script: '--if-present distclean'
|
||||
install-script: 'pnpm install --filter="@woocommerce/plugin-woocommerce..." --frozen-lockfile --config.dedupe-peer-dependents=false'
|
||||
build-script: '--filter="@woocommerce/plugin-woocommerce" build'
|
||||
clean-script: '--if-present buildclean'
|
||||
minimum-change-threshold: 100
|
||||
omit-unchanged: true
|
||||
|
|
|
@ -8,6 +8,8 @@ on:
|
|||
- '**/changelog/**'
|
||||
- '**/tests/**'
|
||||
- '**/*.md'
|
||||
- '.github/**'
|
||||
- '!.github/workflows/pr-build-live-branch.yml'
|
||||
|
||||
concurrency:
|
||||
# Cancel concurrent jobs on pull_request but not push, by including the run_id in the concurrency group for the latter.
|
||||
|
@ -39,12 +41,8 @@ jobs:
|
|||
|
||||
- name: Setup WooCommerce Monorepo
|
||||
uses: ./.github/actions/setup-woocommerce-monorepo
|
||||
|
||||
- name: Cache PNPM Dependencies
|
||||
uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
cache: pnpm
|
||||
pull-package-deps: '@woocommerce/plugin-woocommerce'
|
||||
|
||||
- name: Prepare plugin zips
|
||||
id: prepare
|
||||
|
|
|
@ -17,6 +17,8 @@ jobs:
|
|||
with:
|
||||
install: 'code-analyzer...'
|
||||
build: 'code-analyzer'
|
||||
pull-package-deps: 'code-analyzer'
|
||||
|
||||
- name: 'Analyze'
|
||||
id: 'analyze'
|
||||
working-directory: 'tools/code-analyzer'
|
||||
|
|
|
@ -26,6 +26,7 @@ jobs:
|
|||
with:
|
||||
install: true
|
||||
build: './tools/package-release'
|
||||
pull-package-deps: 'tools/package-release'
|
||||
|
||||
- name: Execute script
|
||||
run: ./tools/package-release/bin/dev prepare ${{ github.event.inputs.packages }}
|
||||
|
|
|
@ -191,6 +191,8 @@ jobs:
|
|||
|
||||
- name: Setup WooCommerce Monorepo
|
||||
uses: ./.github/actions/setup-woocommerce-monorepo
|
||||
with:
|
||||
pull-package-deps: '@woocommerce/plugin-woocommerce'
|
||||
|
||||
- name: Build zip
|
||||
working-directory: plugins/woocommerce
|
||||
|
@ -219,6 +221,8 @@ jobs:
|
|||
|
||||
- name: Setup WooCommerce Monorepo
|
||||
uses: ./.github/actions/setup-woocommerce-monorepo
|
||||
with:
|
||||
pull-package-deps: '@woocommerce/plugin-woocommerce'
|
||||
|
||||
- name: Build zip
|
||||
working-directory: plugins/woocommerce
|
||||
|
|
|
@ -22,6 +22,7 @@ jobs:
|
|||
with:
|
||||
install: '@woocommerce/plugin-woocommerce'
|
||||
build: '@woocommerce/plugin-woocommerce'
|
||||
pull-package-deps: '@woocommerce/plugin-woocommerce-beta-tester'
|
||||
|
||||
- name: Lint
|
||||
working-directory: plugins/woocommerce-beta-tester
|
||||
|
|
|
@ -1,66 +1,70 @@
|
|||
name: Remind reviewers to also review the testing instructions.
|
||||
name: Remind reviewers to also review the testing instructions and test coverage
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [review_requested]
|
||||
pull_request_target:
|
||||
types: [review_requested]
|
||||
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
add-testing-instructions-review-comment:
|
||||
runs-on: ubuntu-20.04
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
add-testing-instructions-review-comment:
|
||||
runs-on: ubuntu-20.04
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c
|
||||
|
||||
- name: Install Octokit
|
||||
run: npm --prefix .github/workflows/scripts install @octokit/action@~6.1.0
|
||||
- name: Install Octokit
|
||||
run: npm --prefix .github/workflows/scripts install @octokit/action@~6.1.0
|
||||
|
||||
- name: Install Actions Core
|
||||
run: npm --prefix .github/workflows/scripts install @actions/core@~1.10.1
|
||||
- name: Install Actions Core
|
||||
run: npm --prefix .github/workflows/scripts install @actions/core@~1.10.1
|
||||
|
||||
- name: Check if user is a community contributor
|
||||
id: is-community-contributor
|
||||
run: node .github/workflows/scripts/is-community-contributor.js
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Check if user is a community contributor
|
||||
id: is-community-contributor
|
||||
run: node .github/workflows/scripts/is-community-contributor.js
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get the username of requested reviewers
|
||||
if: steps.is-community-contributor.outputs.is-community == 'no'
|
||||
id: get_reviewer_username
|
||||
run: |
|
||||
# Retrieves the username of all reviewers and stores them in a comma-separated list
|
||||
reviewers=$(echo '${{ toJson(github.event.pull_request.requested_reviewers[*].login) }}' | jq -r 'map("@\(.)") | join(", ")')
|
||||
echo "REVIEWERS=$reviewers" >> $GITHUB_ENV
|
||||
- name: Get the username of requested reviewers
|
||||
if: steps.is-community-contributor.outputs.is-community == 'no'
|
||||
id: get_reviewer_username
|
||||
run: |
|
||||
# Retrieves the username of all reviewers and stores them in a comma-separated list
|
||||
reviewers=$(echo '${{ toJson(github.event.pull_request.requested_reviewers[*].login) }}' | jq -r 'map("@\(.)") | join(", ")')
|
||||
echo "REVIEWERS=$reviewers" >> $GITHUB_ENV
|
||||
|
||||
- name: Get the name of requested teams
|
||||
id: get_team_name
|
||||
run: |
|
||||
# Retrieves the name of all teams asked for review and stores them in a comma-separated list
|
||||
teams=$(echo '${{ toJson(github.event.pull_request.requested_teams[*].slug) }}' | jq -r 'map("@woocommerce/\(.)") | join(", ")')
|
||||
echo "TEAMS=$teams" >> $GITHUB_ENV
|
||||
- name: Get the name of requested teams
|
||||
id: get_team_name
|
||||
run: |
|
||||
# Retrieves the name of all teams asked for review and stores them in a comma-separated list
|
||||
teams=$(echo '${{ toJson(github.event.pull_request.requested_teams[*].slug) }}' | jq -r 'map("@woocommerce/\(.)") | join(", ")')
|
||||
echo "TEAMS=$teams" >> $GITHUB_ENV
|
||||
|
||||
- name: Find the comment by github-actions[bot] asking for reviewing the testing instructions
|
||||
uses: peter-evans/find-comment@034abe94d3191f9c89d870519735beae326f2bdb
|
||||
id: find-comment
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-author: 'github-actions[bot]'
|
||||
body-includes: please make sure to review the testing instructions
|
||||
- name: Find the comment by github-actions[bot] asking for reviewing the testing instructions
|
||||
uses: peter-evans/find-comment@034abe94d3191f9c89d870519735beae326f2bdb
|
||||
id: find-comment
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-author: 'github-actions[bot]'
|
||||
body-includes: please make sure to review the testing instructions
|
||||
|
||||
- name: Create or update PR comment asking for reviewers to review the testing instructions
|
||||
uses: peter-evans/create-or-update-comment@67dcc547d311b736a8e6c5c236542148a47adc3d
|
||||
with:
|
||||
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
Hi ${{ env.REVIEWERS }}, ${{ env.TEAMS }}
|
||||
|
||||
Apart from reviewing the code changes, please make sure to review the testing instructions as well.
|
||||
|
||||
You can follow this guide to find out what good testing instructions should look like:
|
||||
https://github.com/woocommerce/woocommerce/wiki/Writing-high-quality-testing-instructions
|
||||
edit-mode: replace
|
||||
- name: Create or update PR comment asking for reviewers to review the testing instructions and test coverage
|
||||
uses: peter-evans/create-or-update-comment@67dcc547d311b736a8e6c5c236542148a47adc3d
|
||||
with:
|
||||
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
Hi ${{ env.REVIEWERS }}, ${{ env.TEAMS }}
|
||||
|
||||
Apart from reviewing the code changes, please make sure to review the testing instructions and verify that relevant tests (E2E, Unit, Integration, etc.) have been added or updated as needed.
|
||||
|
||||
You can follow this guide to find out what good testing instructions should look like:
|
||||
https://github.com/woocommerce/woocommerce/wiki/Writing-high-quality-testing-instructions
|
||||
edit-mode: replace
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
/* eslint-disable no-console */
|
||||
const fs = require( 'fs' );
|
||||
|
||||
const { RELEASE_TAG, ARTIFACT_NAME, WP_ENV_CONFIG_PATH } = process.env;
|
||||
|
||||
if ( ! RELEASE_TAG ) {
|
||||
console.error( 'Please set the RELEASE_TAG environment variable!' );
|
||||
process.exit( 1 );
|
||||
}
|
||||
|
||||
if ( ! ARTIFACT_NAME ) {
|
||||
console.error( 'Please set the ARTIFACT_NAME environment variable!' );
|
||||
process.exit( 1 );
|
||||
}
|
||||
|
||||
if ( ! WP_ENV_CONFIG_PATH ) {
|
||||
console.error( 'Please set the WP_ENV_CONFIG_PATH environment variable!' );
|
||||
process.exit( 1 );
|
||||
}
|
||||
|
||||
const artifactUrl = `https://github.com/woocommerce/woocommerce/releases/download/${ RELEASE_TAG }/${ ARTIFACT_NAME }`;
|
||||
|
||||
const configPath = `${ WP_ENV_CONFIG_PATH }/.wp-env.json`;
|
||||
console.log( `Reading ${ configPath }` );
|
||||
const data = fs.readFileSync( configPath, 'utf8' );
|
||||
const wpEnvConfig = JSON.parse( data );
|
||||
|
||||
const overrideConfig = {};
|
||||
|
||||
if ( wpEnvConfig.plugins ) {
|
||||
overrideConfig.plugins = wpEnvConfig.plugins;
|
||||
}
|
||||
|
||||
if ( wpEnvConfig.env?.tests?.plugins ) {
|
||||
overrideConfig.env = {
|
||||
tests: {
|
||||
plugins: wpEnvConfig.env.tests.plugins,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const entriesToReplace = [ '.', '../woocommerce' ];
|
||||
|
||||
for ( const entry of entriesToReplace ) {
|
||||
// Search and replace in root plugins
|
||||
let found = overrideConfig.plugins.indexOf( entry );
|
||||
if ( found >= 0 ) {
|
||||
console.log(
|
||||
`Replacing ${ entry } with ${ artifactUrl } in root plugins`
|
||||
);
|
||||
overrideConfig.plugins[ found ] = artifactUrl;
|
||||
}
|
||||
|
||||
// Search and replace in test env plugins
|
||||
found = overrideConfig.env?.tests?.plugins?.indexOf( entry );
|
||||
if ( found >= 0 ) {
|
||||
console.log(
|
||||
`Replacing ${ entry } with ${ artifactUrl } in env.tests.plugins`
|
||||
);
|
||||
overrideConfig.env.tests.plugins[ found ] = artifactUrl;
|
||||
}
|
||||
}
|
||||
|
||||
const overrideConfigPath = `${ WP_ENV_CONFIG_PATH }/.wp-env.override.json`;
|
||||
console.log( `Saving ${ overrideConfigPath }` );
|
||||
fs.writeFileSync(
|
||||
overrideConfigPath,
|
||||
JSON.stringify( overrideConfig, null, 2 )
|
||||
);
|
|
@ -7,6 +7,7 @@ on:
|
|||
jobs:
|
||||
run-tests:
|
||||
name: 'Run tests'
|
||||
if: github.repository == 'woocommerce/woocommerce'
|
||||
uses: ./.github/workflows/ci.yml
|
||||
with:
|
||||
trigger: 'daily-checks'
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
name: 'On demand tests run'
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
trigger:
|
||||
type: choice
|
||||
description: 'Event name: it will be used to filter the jobs to run in ci.yml.'
|
||||
required: true
|
||||
options:
|
||||
- push
|
||||
- daily-checks
|
||||
- pre-release
|
||||
- on-demand
|
||||
- custom
|
||||
default: on-demand
|
||||
custom-trigger:
|
||||
type: string
|
||||
description: 'Custom event name: In case the `Event name` choice is `custom`, this field is required.'
|
||||
required: false
|
||||
|
||||
jobs:
|
||||
validate-input:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Validate input'
|
||||
run: |
|
||||
if [ "${{ inputs.trigger }}" == "custom" ] && [ -z "${{ inputs.custom-trigger }}" ]; then
|
||||
echo "Custom event name is required when event name choice `custom`."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
run-tests:
|
||||
name: 'Run tests'
|
||||
uses: ./.github/workflows/ci.yml
|
||||
with:
|
||||
trigger: ${{ inputs.trigger == 'custom' && inputs.custom-trigger || inputs.trigger }}
|
||||
secrets: inherit
|
41
.syncpackrc
41
.syncpackrc
|
@ -41,7 +41,43 @@
|
|||
"@types/*",
|
||||
"@typescript-eslint/*",
|
||||
"@woocommerce/*",
|
||||
"@wordpress/*",
|
||||
"@wordpress/api-fetch",
|
||||
"@wordpress/autop",
|
||||
"@wordpress/babel-preset-default",
|
||||
"@wordpress/base-styles",
|
||||
"@wordpress/block-editor",
|
||||
"@wordpress/blocks",
|
||||
"@wordpress/browserslist-config",
|
||||
"@wordpress/components",
|
||||
"@wordpress/compose",
|
||||
"@wordpress/core-data",
|
||||
"@wordpress/data",
|
||||
"@wordpress/data-controls",
|
||||
"@wordpress/date",
|
||||
"@wordpress/dependency-extraction-webpack-plugin",
|
||||
"@wordpress/deprecated",
|
||||
"@wordpress/dom",
|
||||
"@wordpress/dom-ready",
|
||||
"@wordpress/e2e-test-utils",
|
||||
"@wordpress/e2e-test-utils-playwright",
|
||||
"@wordpress/e2e-tests",
|
||||
"@wordpress/element",
|
||||
"@wordpress/html-entities",
|
||||
"@wordpress/i18n",
|
||||
"@wordpress/icons",
|
||||
"@wordpress/is-shallow-equal",
|
||||
"@wordpress/notices",
|
||||
"@wordpress/plugins",
|
||||
"@wordpress/postcss-plugins-preset",
|
||||
"@wordpress/postcss-themes",
|
||||
"@wordpress/prettier-config",
|
||||
"@wordpress/primitives",
|
||||
"@wordpress/scripts",
|
||||
"@wordpress/server-side-render",
|
||||
"@wordpress/style-engine",
|
||||
"@wordpress/stylelint-config",
|
||||
"@wordpress/url",
|
||||
"@wordpress/wordcount",
|
||||
"babel*",
|
||||
"eslint*",
|
||||
"glob*",
|
||||
|
@ -205,9 +241,10 @@
|
|||
"@wordpress/env"
|
||||
],
|
||||
"packages": [
|
||||
"@woocommerce/block-library",
|
||||
"**"
|
||||
],
|
||||
"pinVersion": "^9.0.7"
|
||||
"pinVersion": "^9.7.0"
|
||||
},
|
||||
{
|
||||
"dependencies": [
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
diff --git a/build-module/lock-unlock.js b/build-module/lock-unlock.js
|
||||
index 2265f933ceec19f65ca6776c24c3f88b368d713f..e9e10980bfd1b584ab0a037c3b72edae29a2a26e 100644
|
||||
--- a/build-module/lock-unlock.js
|
||||
+++ b/build-module/lock-unlock.js
|
||||
@@ -1,9 +1,34 @@
|
||||
/**
|
||||
- * WordPress dependencies
|
||||
+ * External dependencies
|
||||
*/
|
||||
import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/private-apis';
|
||||
-export const {
|
||||
- lock,
|
||||
- unlock
|
||||
-} = __dangerousOptInToUnstableAPIsOnlyForCoreModules('I know using unstable features means my plugin or theme will inevitably break on the next WordPress release.', '@wordpress/edit-site');
|
||||
+
|
||||
+// Workaround for Gutenberg private API consent string differences between WP 6.3 and 6.4+
|
||||
+// The modified version checks for the WP version and replaces the consent string with the correct one.
|
||||
+// This can be removed once we drop support for WP 6.3 in the "Customize Your Store" task.
|
||||
+// See this PR for details: https://github.com/woocommerce/woocommerce/pull/40884
|
||||
+
|
||||
+const wordPressConsentString = {
|
||||
+ 6.4: 'I know using unstable features means my plugin or theme will inevitably break on the next WordPress release.',
|
||||
+ 6.5: 'I know using unstable features means my theme or plugin will inevitably break in the next version of WordPress.',
|
||||
+ 6.6: 'I acknowledge private features are not for use in themes or plugins and doing so will break in the next version of WordPress.',
|
||||
+};
|
||||
+
|
||||
+function optInToUnstableAPIs() {
|
||||
+ let error;
|
||||
+ for ( const optInString of Object.values( wordPressConsentString ) ) {
|
||||
+ try {
|
||||
+ return __dangerousOptInToUnstableAPIsOnlyForCoreModules(
|
||||
+ optInString,
|
||||
+ '@wordpress/edit-site'
|
||||
+ );
|
||||
+ } catch ( anError ) {
|
||||
+ error = anError;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ throw error;
|
||||
+}
|
||||
+
|
||||
+export const { lock, unlock } = optInToUnstableAPIs();
|
||||
//# sourceMappingURL=lock-unlock.js.map
|
254
changelog.txt
254
changelog.txt
|
@ -1,5 +1,255 @@
|
|||
== Changelog ==
|
||||
|
||||
= 9.1.4 2024-07-26 =
|
||||
|
||||
**WooCommerce**
|
||||
|
||||
* Fix - Revert fixing terms count in tracking PR as it caused product_add_publish to be triggered more than usual. [#49797](https://github.com/woocommerce/woocommerce/pull/49797)
|
||||
* Fix - Hardening against XSS via the Product Button unescaped attribute. [#50010](https://github.com/woocommerce/woocommerce/pull/50010)
|
||||
* Fix - Enhance escaping for block attributes. [#50015](https://github.com/woocommerce/woocommerce/pull/50015)
|
||||
|
||||
|
||||
= 9.1.2 2024-07-12 =
|
||||
|
||||
**WooCommerce**
|
||||
|
||||
* Fix - Revert 46857 to preserve backcompat with earlier WC versions. [#48753](https://github.com/woocommerce/woocommerce/pull/48753)
|
||||
|
||||
= 9.1.1 2024-07-11 =
|
||||
|
||||
**WooCommerce**
|
||||
|
||||
* Tweak - Revert #46262, as that PR would render input values invisible under certain conditions. [49404](https://github.com/woocommerce/woocommerce/pull/49404)
|
||||
|
||||
= 9.1.0 2024-07-10 =
|
||||
|
||||
**WooCommerce**
|
||||
|
||||
* Fix - Prevent HTML tags being rendered on order confirmation and emails [#49370](https://github.com/woocommerce/woocommerce/pull/49370)
|
||||
* Security - Improve the way we cache information about recent customer activity, to prevent the wrong data being retrieved in some specific conditions involving multisite networks. [#49373](https://github.com/woocommerce/woocommerce/pull/49373)
|
||||
* Fix - Prevent BatchProcessingController from cleaning up processors after a premature shutdown. [#49243](https://github.com/woocommerce/woocommerce/pull/49243)
|
||||
* Fix - CYS: fix not template set correctly. [#49113](https://github.com/woocommerce/woocommerce/pull/49113)
|
||||
* Fix - CYS: Disable readonly mode only when full composability feature flag is enabled. [#48752](https://github.com/woocommerce/woocommerce/pull/48752)
|
||||
* Fix - CYS: fix crash of CYS on WordPress 6.6 [#48664](https://github.com/woocommerce/woocommerce/pull/48664)
|
||||
* Fix - Revert "Set stock quantity value as 0 by default (#48448)" #48863 [#48863](https://github.com/woocommerce/woocommerce/pull/48863)
|
||||
* Fix - Add product id to product_edit_view track in classic product edit screen [#47853](https://github.com/woocommerce/woocommerce/pull/47853)
|
||||
* Fix - Address responsiveness issues in orders list table. [#47684](https://github.com/woocommerce/woocommerce/pull/47684)
|
||||
* Fix - Add screen-reader-text styles to e-mails. [#47738](https://github.com/woocommerce/woocommerce/pull/47738)
|
||||
* Fix - Adds new hook `woocommerce_rest_delete_shipping_zone_method` which will fire after a shipping zone method is deleted via the REST API. [#47862](https://github.com/woocommerce/woocommerce/pull/47862)
|
||||
* Fix - Allow products with non-integer stock to be created via REST API. [#48541](https://github.com/woocommerce/woocommerce/pull/48541)
|
||||
* Fix - Calling $product->get_status() after $product->save() on a new product now returns correct status. [#48241](https://github.com/woocommerce/woocommerce/pull/48241)
|
||||
* Fix - Change the cursor to a pointer when hovering over the mini cart [#46996](https://github.com/woocommerce/woocommerce/pull/46996)
|
||||
* Fix - CYS - Hovering over the header or footer on the "Design your homepage" section should not make them highlighted. [#48358](https://github.com/woocommerce/woocommerce/pull/48358)
|
||||
* Fix - CYS - Select the next block after deleting the selected one (instead of the header). [#48316](https://github.com/woocommerce/woocommerce/pull/48316)
|
||||
* Fix - CYS: apply white color to the heading elements in the core/cover block. [#48447](https://github.com/woocommerce/woocommerce/pull/48447)
|
||||
* Fix - CYS: Fix crash homepage. [#48205](https://github.com/woocommerce/woocommerce/pull/48205)
|
||||
* Fix - CYS: Fix CSS header. [#48389](https://github.com/woocommerce/woocommerce/pull/48389)
|
||||
* Fix - CYS: fix logic to disable mover buttons. [#48502](https://github.com/woocommerce/woocommerce/pull/48502)
|
||||
* Fix - CYS: fix tooltip position. [#48495](https://github.com/woocommerce/woocommerce/pull/48495)
|
||||
* Fix - CYS: hide popover when the mouse pointer leaves the site preview and then back. [#48394](https://github.com/woocommerce/woocommerce/pull/48394)
|
||||
* Fix - Do not create empty webhooks after failure to deliver deleted webhook. [#48480](https://github.com/woocommerce/woocommerce/pull/48480)
|
||||
* Fix - Ensure attribute slugs with multibyte characters are handled property when outputting attributes in the REST API products endpoint [#48198](https://github.com/woocommerce/woocommerce/pull/48198)
|
||||
* Fix - Ensure available stock is updated correctly when updating line items in orders via the REST API. [#47784](https://github.com/woocommerce/woocommerce/pull/47784)
|
||||
* Fix - Ensure data filtered by `woocommerce_logger_log_message` does not carry across multiple log handlers [#48336](https://github.com/woocommerce/woocommerce/pull/48336)
|
||||
* Fix - Ensure getPreviousDate default behaviour is comparing previous_year [#47951](https://github.com/woocommerce/woocommerce/pull/47951)
|
||||
* Fix - Ensure permission checks for the customer downloads REST API endpoint use the correct customer ID. [#47854](https://github.com/woocommerce/woocommerce/pull/47854)
|
||||
* Fix - Ensure that data containing multibyte characters and/or slashes that is appended to log entries gets encoded and rendered correctly [#48341](https://github.com/woocommerce/woocommerce/pull/48341)
|
||||
* Fix - Fix a bug with the woocommerce_get_default_value_for_{key} filter that was preventing setting a falsey value on a checkbox (i.e. to uncheck it dynamically) [#48031](https://github.com/woocommerce/woocommerce/pull/48031)
|
||||
* Fix - Fix activation limit for single license subscriptions on woocommerce.com [#47643](https://github.com/woocommerce/woocommerce/pull/47643)
|
||||
* Fix - Fix a null parameter being passed into strpos in Admin/Orders/PageController.php [#48476](https://github.com/woocommerce/woocommerce/pull/48476)
|
||||
* Fix - Fix bug where Core Profiler initiates a Jetpack connection even if it was already connected before [#48345](https://github.com/woocommerce/woocommerce/pull/48345)
|
||||
* Fix - Fix bumped down data when analytics chart current period contains 29th Feb [#45874](https://github.com/woocommerce/woocommerce/pull/45874)
|
||||
* Fix - Fix coming soon footer banner doesn't display properly on tablet and mobile [#47980](https://github.com/woocommerce/woocommerce/pull/47980)
|
||||
* Fix - Fix e2e tests about the tabs selection during the product creation experience [#47860](https://github.com/woocommerce/woocommerce/pull/47860)
|
||||
* Fix - Fix edit variable product test [#48288](https://github.com/woocommerce/woocommerce/pull/48288)
|
||||
* Fix - Fix FlexSlider thumbnail animation for variable products with default form values on small devices. </details> <details> [#48137](https://github.com/woocommerce/woocommerce/pull/48137)
|
||||
* Fix - Fix location settings not updated in tax task [#48606](https://github.com/woocommerce/woocommerce/pull/48606)
|
||||
* Fix - Fix LYS private link URL parameter regardless of permalink settings [#48425](https://github.com/woocommerce/woocommerce/pull/48425)
|
||||
* Fix - Fix product archive page not hidden behind the coming soon page [#48522](https://github.com/woocommerce/woocommerce/pull/48522)
|
||||
* Fix - Fix Product Gallery block error on revisiting Single Product template without fully reloading the page. [#47636](https://github.com/woocommerce/woocommerce/pull/47636)
|
||||
* Fix - Fix product tracks when importing #47857 [#47857](https://github.com/woocommerce/woocommerce/pull/47857)
|
||||
* Fix - Fix some issues in performance tests #47735 [#47735](https://github.com/woocommerce/woocommerce/pull/47735)
|
||||
* Fix - Fix the issue that the React-powered admin routing pages added after the filter initialization could not be displayed. [#47696](https://github.com/woocommerce/woocommerce/pull/47696)
|
||||
* Fix - Fix the terms counts in wcadmin_product_add_publish event. [#48194](https://github.com/woocommerce/woocommerce/pull/48194)
|
||||
* Fix - Fix two products being added to cart when Geolocate (with page caching support) was enabled and AJAX add to cart buttons disabled [#47761](https://github.com/woocommerce/woocommerce/pull/47761)
|
||||
* Fix - Fix untranslated strings on CYS and marketplace [#48127](https://github.com/woocommerce/woocommerce/pull/48127)
|
||||
* Fix - Honor empty "additional content" setting in e-mails. [#47809](https://github.com/woocommerce/woocommerce/pull/47809)
|
||||
* Fix - Improve consistency of Setting-> Gateway Manage button for WooPayments gateway [#48212](https://github.com/woocommerce/woocommerce/pull/48212)
|
||||
* Fix - In general, the `last_access` field of a REST API key should only be updated once-per-request.
|
||||
* Fix - Make coupon metadata read robust against wrongly stored product related metadata [#48362](https://github.com/woocommerce/woocommerce/pull/48362)
|
||||
* Fix - Moved WooCommerce block categories registration on the server-side, fixing a bug that would show warnings to developers trying to hook new blocks in such categories. [#47836](https://github.com/woocommerce/woocommerce/pull/47836)
|
||||
* Fix - Possible availability of unpublished coupons on sites with an object cache has been addressed through improved cache management. [#47739](https://github.com/woocommerce/woocommerce/pull/47739)
|
||||
* Fix - Prefer update URLs over PluginURI in My Subscriptions for plugins without a subscription. [#47950](https://github.com/woocommerce/woocommerce/pull/47950)
|
||||
* Fix - Prevent on-sale badge from showing on top of the coming soon banner. [#48082](https://github.com/woocommerce/woocommerce/pull/48082)
|
||||
* Fix - Prevent Product Gallery from being inserted on Posts and Pages. [#48228](https://github.com/woocommerce/woocommerce/pull/48228)
|
||||
* Fix - Product Collection: prevent throwing warnings in some circumstances when rendering block [#48530](https://github.com/woocommerce/woocommerce/pull/48530)
|
||||
* Fix - Product Price: Narrow down the ancestors of the block so it's available in inserter only in places where block makes sense [#47802](https://github.com/woocommerce/woocommerce/pull/47802)
|
||||
* Fix - Re-enable variable product E2E test #48294 [#48294](https://github.com/woocommerce/woocommerce/pull/48294)
|
||||
* Fix - Related Products: hides unusable options from Inspector Controls [#47845](https://github.com/woocommerce/woocommerce/pull/47845)
|
||||
* Fix - Run possibly_add_template_id function in woocommerce_rest_prepare_product_variation_object hook [#48325](https://github.com/woocommerce/woocommerce/pull/48325)
|
||||
* Fix - Scroll to view the templates section on the status page [#48125](https://github.com/woocommerce/woocommerce/pull/48125)
|
||||
* Fix - Set stock quantity value as 0 by default #48448 [#48448](https://github.com/woocommerce/woocommerce/pull/48448)
|
||||
* Fix - Update plugin installation error logger to use plugin track key for extension name [#47786](https://github.com/woocommerce/woocommerce/pull/47786)
|
||||
* Fix - When a product attribute is updated, unchanged values should not be reset to their defaults. [#48120](https://github.com/woocommerce/woocommerce/pull/48120)
|
||||
* Fix - WooCommerce: fixes the checks when migrating the product form template [#48386](https://github.com/woocommerce/woocommerce/pull/48386)
|
||||
* Fix - [CYS Full Composability] Ensure that the assembler doesn't crash when the feature flag is enabled, but the site doesn't have the latest version of Gutenberg. [#47546](https://github.com/woocommerce/woocommerce/pull/47546)
|
||||
* Add - Add CLI tools for the product attributes lookup table [#47311](https://github.com/woocommerce/woocommerce/pull/47311)
|
||||
* Add - Add 'woocommerce_order_note_deleted' hook for order note deletions. [#47916](https://github.com/woocommerce/woocommerce/pull/47916)
|
||||
* Add - Add CLI tools to enable and disable HPOS compatibility mode. [#48117](https://github.com/woocommerce/woocommerce/pull/48117)
|
||||
* Add - Added 'woocommerce_restore_order_item_stock' filter for restored line item stock on canceled orders [#40848](https://github.com/woocommerce/woocommerce/pull/40848)
|
||||
* Add - Add ErrorBoundary component for handling unexpect errors [#48250](https://github.com/woocommerce/woocommerce/pull/48250)
|
||||
* Add - Add filter to dynamically exclude a page from Coming soon mode [#47787](https://github.com/woocommerce/woocommerce/pull/47787)
|
||||
* Add - Add Printful product placement to Add Products task [#48520](https://github.com/woocommerce/woocommerce/pull/48520)
|
||||
* Add - Add skipped test custom reporter to surface skipped tests in CI runs [#48195](https://github.com/woocommerce/woocommerce/pull/48195)
|
||||
* Add - Add the ability to test experimental blocks via the Advanced > Features menu of WooCommerce settings. [#47701](https://github.com/woocommerce/woocommerce/pull/47701)
|
||||
* Add - Add woocommerce_manage_stock option to the default_option_permissions list in the Options rest controller [#48239](https://github.com/woocommerce/woocommerce/pull/48239)
|
||||
* Add - CYS: add CTA to our Fiverr Logo Maker landing page. [#48486](https://github.com/woocommerce/woocommerce/pull/48486)
|
||||
* Add - CYS: add pattern category in the block toolbar. [#48501](https://github.com/woocommerce/woocommerce/pull/48501)
|
||||
* Add - CYS: Add the Delete button to the Block Toolbar. [#48143](https://github.com/woocommerce/woocommerce/pull/48143)
|
||||
* Add - CYS: Ensure that toolbar appears only when the homepage sidebar is open. [#48115](https://github.com/woocommerce/woocommerce/pull/48115)
|
||||
* Add - CYS: Show Patterns from PTK. [#48207](https://github.com/woocommerce/woocommerce/pull/48207)
|
||||
* Add - CYS: Show popover when the user clicks on the pattern [#47583](https://github.com/woocommerce/woocommerce/pull/47583)
|
||||
* Add - Determine _product_template_id from 'woocommerce_product_editor_determine_product_template' filter [#47762](https://github.com/woocommerce/woocommerce/pull/47762)
|
||||
* Add - Display an admin notice in Setting and Extension pages when there are expiring subscriptions and connected account doesn't have a payment method. [#47141](https://github.com/woocommerce/woocommerce/pull/47141)
|
||||
* Add - Enhancements to background batch processing. [#48078](https://github.com/woocommerce/woocommerce/pull/48078)
|
||||
* Add - Highlight the pattern when the user hovers it. [#47415](https://github.com/woocommerce/woocommerce/pull/47415)
|
||||
* Add - LYS - Add 'Remove test orders' for WooPayments [#47832](https://github.com/woocommerce/woocommerce/pull/47832)
|
||||
* Add - PFT: introduce controller and initialize it [#48221](https://github.com/woocommerce/woocommerce/pull/48221)
|
||||
* Add - REST API: extened shipping_classes namespace with the /suggest-slug endpoint [#47896](https://github.com/woocommerce/woocommerce/pull/47896)
|
||||
* Add - Updated shipstation copy [#48549](https://github.com/woocommerce/woocommerce/pull/48549)
|
||||
* Add - WooCommerce: create a new product_form CPT [#48073](https://github.com/woocommerce/woocommerce/pull/48073)
|
||||
* Add - WooCommerce: introduce `product-editor-template-system` feature flag [#48136](https://github.com/woocommerce/woocommerce/pull/48136)
|
||||
* Add - WooCommerce: update CPT product_form posts when plugin updates [#48265](https://github.com/woocommerce/woocommerce/pull/48265)
|
||||
* Add - WooCommerce Blocks: Added a GitHub Action to create issues for flaky E2E tests [#47758](https://github.com/woocommerce/woocommerce/pull/47758)
|
||||
* Update - Add feature flag for Printful placement [#49104](https://github.com/woocommerce/woocommerce/pull/49104)
|
||||
* Update - Add a control to enable a separator on the Checkout block's "Checkout Terms" block. This will enable a separator above the block that can be turned off in case the block is moved. [#47565](https://github.com/woocommerce/woocommerce/pull/47565)
|
||||
* Update - Change the item schemas for Orders and Order Refunds API endpoints to correctly specify that the rate_id property in a tax_line object is an integer, not a string [#47779](https://github.com/woocommerce/woocommerce/pull/47779)
|
||||
* Update - Clean up theming sections in WooCommerce blocks docs [#48420](https://github.com/woocommerce/woocommerce/pull/48420)
|
||||
* Update - CYS - Exclude two testimonials patterns from registering since they depend on Jetpack. [#48233](https://github.com/woocommerce/woocommerce/pull/48233)
|
||||
* Update - CYS - Fix active/inactive patterns for each of the sections in the assembler. [#48458](https://github.com/woocommerce/woocommerce/pull/48458)
|
||||
* Update - CYS - Install the patterns during the CYS flow if the transient is not set. [#48274](https://github.com/woocommerce/woocommerce/pull/48274)
|
||||
* Update - CYS - Redirect to the same section after installing fonts or patterns on the assembler. [#48227](https://github.com/woocommerce/woocommerce/pull/48227)
|
||||
* Update - CYS - Show tooltips on the Shuffle and Delete buttons in the assembler toolbar. [#48465](https://github.com/woocommerce/woocommerce/pull/48465)
|
||||
* Update - CYS: set new default patterns. [#48467](https://github.com/woocommerce/woocommerce/pull/48467)
|
||||
* Update - Display return to cart link on mobile devices. [#48103](https://github.com/woocommerce/woocommerce/pull/48103)
|
||||
* Update - Docs: update documentation regarding Compatibility Layer [#48456](https://github.com/woocommerce/woocommerce/pull/48456)
|
||||
* Update - Expand block templates documentation [#48247](https://github.com/woocommerce/woocommerce/pull/48247)
|
||||
* Update - Experimental blocks now have "(Experimental)" suffix [#48071](https://github.com/woocommerce/woocommerce/pull/48071)
|
||||
* Update - fix: label improvement on my order page template [#48374](https://github.com/woocommerce/woocommerce/pull/48374)
|
||||
* Update - Improve WooCommerce block template names in the Add New Template screen. [#48106](https://github.com/woocommerce/woocommerce/pull/48106)
|
||||
* Update - Invalidate cache for SiteGround Speed Optimizer [#48523](https://github.com/woocommerce/woocommerce/pull/48523)
|
||||
* Update - Optimize the regeneration of the product attributes lookup table [#47700](https://github.com/woocommerce/woocommerce/pull/47700)
|
||||
* Update - Product Archive templates: Replace the default block from Products (Beta) to Product Collection block [#48112](https://github.com/woocommerce/woocommerce/pull/48112)
|
||||
* Update - Product Block Editor: disable the `product-editor-template-system` feature flag as default, even for the development environment. [#48378](https://github.com/woocommerce/woocommerce/pull/48378)
|
||||
* Update - Product Collection: Handpicked Products filter now allows searching from 2 characters and more and updates available results as you type [#48379](https://github.com/woocommerce/woocommerce/pull/48379)
|
||||
* Update - Product Elements: hide Product Summary from Single Product block and only show Excerpt variation [#48253](https://github.com/woocommerce/woocommerce/pull/48253)
|
||||
* Update - Product Rating Stars and Product Rating Counter from the inserter [#48229](https://github.com/woocommerce/woocommerce/pull/48229)
|
||||
* Update - Products (Beta): hide block from inserter in favor of Product Collection block [#48204](https://github.com/woocommerce/woocommerce/pull/48204)
|
||||
* Update - Product Summary: Increase the length of the description from 55 to 100 words (max supported by core/post-excerpt) [#47651](https://github.com/woocommerce/woocommerce/pull/47651)
|
||||
* Update - Reduced the number of FlexSlider animation engines from 2 to 1, now always using CSS3 transitions. [#46564](https://github.com/woocommerce/woocommerce/pull/46564)
|
||||
* Update - Replace the use of options endpoint with the LYS API endpoint to query woocommerce_admin_launch_your_store_survey_completed option. [#47915](https://github.com/woocommerce/woocommerce/pull/47915)
|
||||
* Update - The archive product title will now be updated to the title of the current shop
|
||||
page. If the page does not exist, it will fall back to "Shop". [#48255](https://github.com/woocommerce/woocommerce/pull/48255)
|
||||
* Update - Toggle LYS feature flag on for 9.1 [#48244](https://github.com/woocommerce/woocommerce/pull/48244)
|
||||
* Update - Update input fields styles of the Checkout block [#46362](https://github.com/woocommerce/woocommerce/pull/46362)
|
||||
* Update - WooCommerce: store the template description in the `product_form` excerpt property. [#48327](https://github.com/woocommerce/woocommerce/pull/48327)
|
||||
* Update - Wrap activity panels in error boundary [#48415](https://github.com/woocommerce/woocommerce/pull/48415)
|
||||
* Update - [CYS] Ensure fetch PTK patterns requests are always done async to improve performance. [#47551](https://github.com/woocommerce/woocommerce/pull/47551)
|
||||
* Update - [CYS] Refactor the pattern registration and add patterns from the PTK API. [#47306](https://github.com/woocommerce/woocommerce/pull/47306)
|
||||
* Update - [CYS] Remove the restriction to TT4 and allow users to proceed to the pattern assembler with any block themes. Update intro page design. [#46916](https://github.com/woocommerce/woocommerce/pull/46916)
|
||||
* Update - [CYS] Show a message when tracking is not allowed in patterns and add the ability for users to opt-in and fetch patterns. </details> <details> [#48095](https://github.com/woocommerce/woocommerce/pull/48095)
|
||||
* Dev - Improve E2E selector by making it stricter. Wait for text due to AJAX call. [#48471](https://github.com/woocommerce/woocommerce/pull/48471)
|
||||
* Dev - Added e2e test to check ability to connect to woocommerce.com [#48028](https://github.com/woocommerce/woocommerce/pull/48028)
|
||||
* Dev - Added test enviornments [#48101](https://github.com/woocommerce/woocommerce/pull/48101)
|
||||
* Dev - Add previous error class to checkout endpoint response [#47489](https://github.com/woocommerce/woocommerce/pull/47489)
|
||||
* Dev - Add test for wcpay_connect_account_clicked track [#48347](https://github.com/woocommerce/woocommerce/pull/48347)
|
||||
* Dev - Add tests for some product editor tracks [#48245](https://github.com/woocommerce/woocommerce/pull/48245)
|
||||
* Dev - Blocks E2E: Remove confusing utilities in favor of native locator functionality. [#47904](https://github.com/woocommerce/woocommerce/pull/47904)
|
||||
* Dev - CI: merge test jobs [#48175](https://github.com/woocommerce/woocommerce/pull/48175)
|
||||
* Dev - Clean up eslint comments after rules update in Blocks E2E tests. [#47875](https://github.com/woocommerce/woocommerce/pull/47875)
|
||||
* Dev - Clean up tasklist progression headercard experiment [#47983](https://github.com/woocommerce/woocommerce/pull/47983)
|
||||
* Dev - Clean up welcome modal code [#48346](https://github.com/woocommerce/woocommerce/pull/48346)
|
||||
* Dev - Do not dismiss the error snackbar automatically, fix E2E test #48192 [#48192](https://github.com/woocommerce/woocommerce/pull/48192)
|
||||
* Dev - E2E test: Improve analytics data spec by disabling the task list reminder bar [#48357](https://github.com/woocommerce/woocommerce/pull/48357)
|
||||
* Dev - E2E tests: configure snapshotPathTemplate [#47773](https://github.com/woocommerce/woocommerce/pull/47773)
|
||||
* Dev - E2E tests: fixing flakiness in checkout block and launch your store tests [#48016](https://github.com/woocommerce/woocommerce/pull/48016)
|
||||
* Dev - E2E tests: fixing flaky assembler homepage test [#48356](https://github.com/woocommerce/woocommerce/pull/48356)
|
||||
* Dev - E2E tests: fixing flaky checkout block test [#48527](https://github.com/woocommerce/woocommerce/pull/48527)
|
||||
* Dev - E2E tests: fixing flaky color palette picker test [#48496](https://github.com/woocommerce/woocommerce/pull/48496)
|
||||
* Dev - E2E tests: fixing flaky connect to woo test [#48613](https://github.com/woocommerce/woocommerce/pull/48613)
|
||||
* Dev - E2E tests: fixing flaky customize store transitional test [#48532](https://github.com/woocommerce/woocommerce/pull/48532)
|
||||
* Dev - E2E tests: fixing flaky logo picker test [#48503](https://github.com/woocommerce/woocommerce/pull/48503)
|
||||
* Dev - E2E tests: fixing flaky merchant create variable product test [#48276](https://github.com/woocommerce/woocommerce/pull/48276)
|
||||
* Dev - E2E tests: fixing flaky merchant customer list test [#48463](https://github.com/woocommerce/woocommerce/pull/48463)
|
||||
* Dev - E2E tests: fixing flaky merchant product attribute test [#48230](https://github.com/woocommerce/woocommerce/pull/48230)
|
||||
* Dev - E2E tests: fixing flaky merchant user create and logging [#48446](https://github.com/woocommerce/woocommerce/pull/48446)
|
||||
* Dev - E2E tests: fixing flaky shopper checkout coupons [#48555](https://github.com/woocommerce/woocommerce/pull/48555)
|
||||
* Dev - E2E tests: fixing flaky shopper search browse products in the shop [#48560](https://github.com/woocommerce/woocommerce/pull/48560)
|
||||
* Dev - E2E tests: fixing flaky store owner core profiler test [#48430](https://github.com/woocommerce/woocommerce/pull/48430)
|
||||
* Dev - E2E tests: fixing skipped mini cart test [#47756](https://github.com/woocommerce/woocommerce/pull/47756)
|
||||
* Dev - E2E tests: fixing skipped tests [#47859](https://github.com/woocommerce/woocommerce/pull/47859)
|
||||
* Dev - E2E tests: improve existing merchant e2e tests for creating page and post [#48162](https://github.com/woocommerce/woocommerce/pull/48162)
|
||||
* Dev - E2E tests: improve existing util for inserting blocks via shortcut [#48225](https://github.com/woocommerce/woocommerce/pull/48225)
|
||||
* Dev - E2E tests: improving cart util and updating relevant tests [#48475](https://github.com/woocommerce/woocommerce/pull/48475)
|
||||
* Dev - E2E tests: updated the test ignore pattern for Gutenberg tests project [#47764](https://github.com/woocommerce/woocommerce/pull/47764)
|
||||
* Dev - E2E tests: update tests checking if blocks can be added [#48211](https://github.com/woocommerce/woocommerce/pull/48211)
|
||||
* Dev - E2E tests: update the report configuration for all core jobs [#48424](https://github.com/woocommerce/woocommerce/pull/48424)
|
||||
* Dev - Fix a filters block e2e test that was mistakenly merged incorrectly. [#48122](https://github.com/woocommerce/woocommerce/pull/48122)
|
||||
* Dev - Fixing a flaky core profiler e2e test [#47917](https://github.com/woocommerce/woocommerce/pull/47917)
|
||||
* Dev - Fix path to test results for api core tests [#48490](https://github.com/woocommerce/woocommerce/pull/48490)
|
||||
* Dev - Implement unit test for tracks wcadmin_page_view, wcadmin_tasklist_view, wcadmin_tasklist_task_completed, wcadmin_tasklist_click [#47876](https://github.com/woocommerce/woocommerce/pull/47876)
|
||||
* Dev - Include blocks e2e in ci.yml [#48224](https://github.com/woocommerce/woocommerce/pull/48224)
|
||||
* Dev - Migrate release smoke workflow to the new CI setup [#48113](https://github.com/woocommerce/woocommerce/pull/48113)
|
||||
* Dev - Product Editor: Move variation pricing fields to General tab. [#48155](https://github.com/woocommerce/woocommerce/pull/48155)
|
||||
* Dev - Remove the isFeaturePlugin function, which was used to turn off experimental block styling (but was non functional). Also remove associated code in FeatureGating class. [#47866](https://github.com/woocommerce/woocommerce/pull/47866)
|
||||
* Dev - Remove WOOCOMMERCE_BLOCKS_PHASE completely from the monorepo, introduce BUNDLE_EXPERIMENTAL_BLOCKS just for the purpose of building/bundling experimental blocks [#47807](https://github.com/woocommerce/woocommerce/pull/47807)
|
||||
* Dev - Skipped flaky test: test_order_updated_webhook_delivered_once [#48064](https://github.com/woocommerce/woocommerce/pull/48064)
|
||||
* Dev - Streamline the implementation of the Blocks' E2E utilities. [#47660](https://github.com/woocommerce/woocommerce/pull/47660)
|
||||
* Dev - Streamline the usage of WP CLI in Blocks E2E tests. [#47869](https://github.com/woocommerce/woocommerce/pull/47869)
|
||||
* Dev - Tweak the paths that should trigger e2e tests. [#48067](https://github.com/woocommerce/woocommerce/pull/48067)
|
||||
* Dev - Unskip some tests that have been skipped for flakiness [#47772](https://github.com/woocommerce/woocommerce/pull/47772)
|
||||
* Dev - Update @wordpress/env version to 9.7.0 [#48443](https://github.com/woocommerce/woocommerce/pull/48443)
|
||||
* Dev - Updated Core Profilers XState version to V5 [#48135](https://github.com/woocommerce/woocommerce/pull/48135)
|
||||
* Dev - Update Playwright from 1.41.1 to 1.44.1 (latest) and fixed tests [#48291](https://github.com/woocommerce/woocommerce/pull/48291)
|
||||
* Dev - Update pnpm-lock with updated React [#47973](https://github.com/woocommerce/woocommerce/pull/47973)
|
||||
* Dev - Update the React version in the pnpm-lock file [#47993](https://github.com/woocommerce/woocommerce/pull/47993)
|
||||
* Dev - Update the URLs for order-related e2e tests to use new URLs from HPOS [#46397](https://github.com/woocommerce/woocommerce/pull/46397)
|
||||
* Dev - [e2e tests] Fix e2e test reports paths [#48320](https://github.com/woocommerce/woocommerce/pull/48320)
|
||||
* Tweak - Update Printful label [#48778](https://github.com/woocommerce/woocommerce/pull/48778)
|
||||
* Tweak - Add a close button to dismiss store alerts [#48453](https://github.com/woocommerce/woocommerce/pull/48453)
|
||||
* Tweak - Adds a defensive check to reduce error log noise when regenerating images. [#47785](https://github.com/woocommerce/woocommerce/pull/47785)
|
||||
* Tweak - Adds best practice advice to the API key generation screen. [#48483](https://github.com/woocommerce/woocommerce/pull/48483)
|
||||
* Tweak - CYS - Update the copy for the intro tour. [#48202](https://github.com/woocommerce/woocommerce/pull/48202)
|
||||
* Tweak - CYS: Refactor routing approach. [#48312](https://github.com/woocommerce/woocommerce/pull/48312)
|
||||
* Tweak - Include 'original_post_status' in HPOS edit form. [#48196](https://github.com/woocommerce/woocommerce/pull/48196)
|
||||
* Tweak - Minor improvements to BlockTemplatesController instantiation [#48107](https://github.com/woocommerce/woocommerce/pull/48107)
|
||||
* Tweak - Only load 'productCount' and 'experimentalBlocksEnabled' settings in admin [#48152](https://github.com/woocommerce/woocommerce/pull/48152)
|
||||
* Tweak - Product Editor: Skip momentarily the 'can create a variation option and publish the product' E2E test [#47618](https://github.com/woocommerce/woocommerce/pull/47618)
|
||||
* Tweak - Remove checkstyle.xml file [#47844](https://github.com/woocommerce/woocommerce/pull/47844)
|
||||
* Tweak - Remove unused woocommerce_task_list_prompt_shown option [#48304](https://github.com/woocommerce/woocommerce/pull/48304)
|
||||
* Tweak - Update coming soon banner text to use translation function [#47742](https://github.com/woocommerce/woocommerce/pull/47742)
|
||||
* Tweak - Update LYS survey completion track props [#47985](https://github.com/woocommerce/woocommerce/pull/47985)
|
||||
* Tweak - Update printful copy. [#48626](https://github.com/woocommerce/woocommerce/pull/48626)
|
||||
* Tweak - Update WC blocks e2e tests to WordPress 6.6 [#48436](https://github.com/woocommerce/woocommerce/pull/48436)
|
||||
* Tweak - Verify if the coming soon cache is displayed when launching the store and alerts the user if it is still present. [#48586](https://github.com/woocommerce/woocommerce/pull/48586)
|
||||
* Performance - Add DISTINCT keyword for smaller response and performance. [#48139](https://github.com/woocommerce/woocommerce/pull/48139)
|
||||
* Performance - CYS - Optimize the `Choose a professionally designed theme` intro page image. [#48566](https://github.com/woocommerce/woocommerce/pull/48566)
|
||||
* Performance - Replaced `classnames` package with the faster and smaller `clsx` package. [#47760](https://github.com/woocommerce/woocommerce/pull/47760)
|
||||
* Performance - Revert changing the title of the edit comments screen when editing a review. [#48485](https://github.com/woocommerce/woocommerce/pull/48485)
|
||||
* Enhancement - Accessibility enhancement for the whole shop accounts section [#47144](https://github.com/woocommerce/woocommerce/pull/47144)
|
||||
* Enhancement - Add information about block/shortcode/template usage on Cart and Checkout pages to the WC system report. [#48300](https://github.com/woocommerce/woocommerce/pull/48300)
|
||||
* Enhancement - CYS: add shuffle feature. [#47356](https://github.com/woocommerce/woocommerce/pull/47356)
|
||||
* Enhancement - CYS: allow to the user to move the pattern. [#47322](https://github.com/woocommerce/woocommerce/pull/47322)
|
||||
* Enhancement - Enhancement editor loading speed [#47425](https://github.com/woocommerce/woocommerce/pull/47425)
|
||||
* Enhancement - Handle core profiler get countries error [#48317](https://github.com/woocommerce/woocommerce/pull/48317)
|
||||
* Enhancement - If a variable product doesn't have a Product Image but variations do have images, the zoom and flex slider will be initiated as expected [#47714](https://github.com/woocommerce/woocommerce/pull/47714)
|
||||
* Enhancement - Improve spacing between steps in the Checkout block on mobile and desktop [#47565](https://github.com/woocommerce/woocommerce/pull/47565)
|
||||
* Enhancement - Increase connection timeout to 30 seconds for the requests in WCCOM connection flow [#47842](https://github.com/woocommerce/woocommerce/pull/47842)
|
||||
* Enhancement - Limit coming soon options API call to home screen [#48303](https://github.com/woocommerce/woocommerce/pull/48303)
|
||||
* Enhancement - Modified order status tooltip labels [#47861](https://github.com/woocommerce/woocommerce/pull/47861)
|
||||
* Enhancement - Optimize text wrapping for wc admin pages [#48131](https://github.com/woocommerce/woocommerce/pull/48131)
|
||||
* Enhancement - Remove the previous product management experience [#47814](https://github.com/woocommerce/woocommerce/pull/47814)
|
||||
|
||||
= 9.0.2 2024-06-24 =
|
||||
|
||||
**WooCommerce**
|
||||
|
@ -21,9 +271,9 @@
|
|||
* Fix - Fix settings-api textarea validation to prevent insertion of iframes in description areas by default [#48432](https://github.com/woocommerce/woocommerce/pull/48432)
|
||||
* Fix - #47626 changed the classes on the legacy admin settings save button and broke saving standard tax rates [#48201](https://github.com/woocommerce/woocommerce/pull/48201)
|
||||
* Fix - Revert "Remove customer-effort-score-tracks" feature flag #48235 [#48235](https://github.com/woocommerce/woocommerce/pull/48235)
|
||||
* Fix - Fix db update notice redirection bug where it redirects without checking for db update action. </details> <details> <summary>Changelog Entry Comment</summary> [#48163](https://github.com/woocommerce/woocommerce/pull/48163)
|
||||
* Fix - Fix db update notice redirection bug where it redirects without checking for db update action. [#48163](https://github.com/woocommerce/woocommerce/pull/48163)
|
||||
* Fix - Add missing URL to discover more link in LYS tour [#48109](https://github.com/woocommerce/woocommerce/pull/48109)
|
||||
* Fix - Fix: "On Sale" collection isn't displaying on Editor side </details> <details> [#47994](https://github.com/woocommerce/woocommerce/pull/47994)
|
||||
* Fix - Fix: "On Sale" collection isn't displaying on Editor side [#47994](https://github.com/woocommerce/woocommerce/pull/47994)
|
||||
* Fix - Make the plugin autoinstall process more robust [#47798](https://github.com/woocommerce/woocommerce/pull/47798)
|
||||
* Fix - Prevent tracking files from being enqueued on the front end. [#47938](https://github.com/woocommerce/woocommerce/pull/47938)
|
||||
* Fix - Fix: Product Collection block does not display properly when editing template/post [#47871](https://github.com/woocommerce/woocommerce/pull/47871)
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
---
|
||||
post_title: Check if a Payment Method Support Refunds, Subscriptions or Pre-orders
|
||||
menu_title: Payment method support for refunds, subscriptions, pre-orders
|
||||
tags: payment-methods
|
||||
current wccom url: https://woocommerce.com/document/check-if-payment-gateway-supports-refunds-subscriptions-preorders/
|
||||
---
|
||||
|
||||
# Check if a Payment Method Support Refunds, Subscriptions or Pre-orders
|
||||
|
||||
If a payment method's documentation doesn’t clearly outline the supported features, you can often find what features are supported by looking at payment methods code.
|
||||
|
||||
Payment methods can add support for certain features from WooCommerce and its extensions. For example, a payment method can support refunds, subscriptions or pre-orders functionality.
|
||||
|
||||
## Simplify Commerce example
|
||||
|
||||
Taking the Simplify Commerce payment method as an example, open the plugin files in your favorite editor and search for `$this->supports`. You'll find the supported features:
|
||||
|
||||
```php
|
||||
class WC_Gateway_Simplify_Commerce extends WC_Payment_Gateway {
|
||||
|
||||
/** * Constructor */
|
||||
public function __construct() {
|
||||
$this->id
|
||||
= 'simplify_commerce';
|
||||
$this->method_title
|
||||
= __( 'Simplify Commerce', 'woocommerce' );
|
||||
$this->method_description = __( 'Take payments via Simplify Commerce - uses simplify.js to create card tokens and the Simplify Commerce SDK. Requires SSL when sandbox is disabled.', 'woocommerce' );
|
||||
$this->has_fields = true;
|
||||
$this->supports = array(
|
||||
'subscriptions',
|
||||
'products',
|
||||
'subscription_cancellation',
|
||||
'subscription_reactivation',
|
||||
'subscription_suspension',
|
||||
'subscription_amount_changes',
|
||||
'subscription_payment_method_change',
|
||||
'subscription_date_changes',
|
||||
'default_credit_card_form',
|
||||
'refunds',
|
||||
'pre-orders'
|
||||
);
|
||||
```
|
||||
|
||||
If you don’t find `$this->supports` in the plugin files, that may mean that the payment method isn’t correctly declaring support for refunds, subscripts or pre-orders.
|
|
@ -0,0 +1,81 @@
|
|||
---
|
||||
post_title: Code snippets for configuring special tax scenarios
|
||||
menu_title: Configuring special tax scenarios
|
||||
tags: code-snippet, tax
|
||||
current wccom url: https://woocommerce.com/document/setting-up-taxes-in-woocommerce/configuring-specific-tax-setups-in-woocommerce/#configuring-special-tax-setups
|
||||
---
|
||||
|
||||
# Code snippets for configuring special tax scenarios
|
||||
|
||||
## Scenario A: Charge the same price regardless of location and taxes
|
||||
|
||||
Scenario A: Charge the same price regardless of location and taxes
|
||||
|
||||
If a store enters product prices including taxes, but levies various location-based tax rates, the prices will appear to change depending on which tax rate is applied. In reality, the base price remains the same, but the taxes influence the total. [Follow this link for a detailed explanation](https://woocommerce.com/document/how-taxes-work-in-woocommerce/#cross-border-taxes).
|
||||
|
||||
Some merchants prefer to dynamically change product base prices to account for the changes in taxes and so keep the total price consistent regardless of tax rate. Enable that functionality by adding the following snippet to your child theme’s functions.php file or via a code snippet plugin.
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
add_filter( 'woocommerce_adjust_non_base_location_prices', '__return_false' );
|
||||
```
|
||||
|
||||
## Scenario B: Charge tax based on the subtotal amount
|
||||
|
||||
The following snippet is useful in case where a store only ads taxes when the subtotal reaches a specified minimum. In the code snippet below that minimum is 110 of the store’s currency. Adjust the snippet according to your requirements.
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
add_filter( 'woocommerce_product_get_tax_class', 'big_apple_get_tax_class', 1, 2 );
|
||||
|
||||
function big_apple_get_tax_class( $tax_class, $product ) {
|
||||
if ( WC()->cart->subtotal <= 110 )
|
||||
$tax_class = 'Zero Rate';
|
||||
|
||||
return $tax_class;
|
||||
}
|
||||
```
|
||||
|
||||
## Scenario C: Apply different tax rates based on the customer role
|
||||
|
||||
Some merchants may require different tax rates to be applied based on a customer role to accommodate for wholesale status or tax exemption.
|
||||
|
||||
To enable this functionality, add the following snippet to your child theme’s functions.php file or via a code snippet plugin. In this snippet, users with “administrator” capabilities will be assigned the **Zero rate tax class**. Adjust it according to your requirements.
|
||||
|
||||
```php
|
||||
<?php
|
||||
/**
|
||||
* Apply a different tax rate based on the user role.
|
||||
*/
|
||||
function wc_diff_rate_for_user( $tax_class, $product ) {
|
||||
if ( is_user_logged_in() && current_user_can( 'administrator' ) ) {
|
||||
$tax_class = 'Zero Rate';
|
||||
}
|
||||
|
||||
return $tax_class;
|
||||
}
|
||||
add_filter( 'woocommerce_product_get_tax_class', 'wc_diff_rate_for_user', 1, 2 );
|
||||
add_filter( 'woocommerce_product_variation_get_tax_class', 'wc_diff_rate_for_user', 1, 2 );
|
||||
```
|
||||
|
||||
## Scenario D: Show 0 value taxes
|
||||
|
||||
Taxes that have 0-value are hidden by default. To show them regardless, add the following snippet to your theme’s functions.php file or via a code snippet plugins:
|
||||
|
||||
```php
|
||||
add_filter( 'woocommerce_order_hide_zero_taxes', '__return_false' );
|
||||
```
|
||||
|
||||
## Scenario E: Suffixes on the main variable product
|
||||
|
||||
One of the tax settings for WooCommerce enables the use of suffixes to add additional information to product prices. It’s available for use with the variations of a variable product, but is disabled at the main variation level as it can impact website performance when there are many variations.
|
||||
|
||||
The method responsible for the related price output can be customized via filter hooks if needed for variable products. This will require customization that can be implemented via this filter:
|
||||
|
||||
```php
|
||||
add_filter( 'woocommerce_show_variation_price', '__return_true' );
|
||||
```
|
||||
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
post_title: Disabling Marketplace Suggestions Programmatically
|
||||
menu_title: Disabling marketplace suggestions
|
||||
current wccom url: https://woocommerce.com/document/woocommerce-marketplace-suggestions-settings/#section-6
|
||||
---
|
||||
|
||||
## Disabling Marketplace Suggestions Programmatically
|
||||
|
||||
For those who prefer to programmatically disable marketplace suggestions that are fetched from woocommerce.com, add the `woocommerce_allow_marketplace_suggestions` filter to your theme’s `functions.php` or a custom plugin.
|
||||
|
||||
For example:
|
||||
|
||||
This filter will completely remove Marketplace Suggestions from your WooCommerce admin.
|
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
post_title: Displaying Custom Fields in Your Theme or Site
|
||||
menu_title: Displaying custom fields in theme
|
||||
tags: code-snippet
|
||||
current wccom url: https://woocommerce.com/document/custom-product-fields/
|
||||
---
|
||||
|
||||
## Displaying Custom Fields in Your Theme or Site
|
||||
|
||||
You can use the metadata from custom fields you add to your products to display the added information within your theme or site.
|
||||
|
||||
To display the custom fields for each product, you have to edit your theme’s files. Here’s an example of how you might display a custom field within the single product pages after the short description:
|
||||
|
||||
![image](https://github.com/woocommerce/woocommerce-developer-advocacy/assets/15178758/ed417ed8-4462-45b9-96b6-c0141afaeb2b)
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
// Display a product custom field within single product pages after the short description
|
||||
|
||||
function woocommerce_custom_field_example() {
|
||||
|
||||
if ( ! is_product() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $product;
|
||||
|
||||
if ( ! is_object( $product ) ) {
|
||||
$product = wc_get_product( get_the_ID() );
|
||||
}
|
||||
|
||||
$custom_field_value = get_post_meta( $product->get_id(), 'woo_custom_field', true );
|
||||
|
||||
if ( ! empty( $custom_field_value ) ) {
|
||||
echo '<div class="custom-field">' . esc_html( $custom_field_value ) . '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
add_action( 'woocommerce_before_add_to_cart_form', 'woocommerce_custom_field_example', 10 );
|
||||
```
|
|
@ -0,0 +1,147 @@
|
|||
---
|
||||
post_title: Free Shipping Customizations
|
||||
menu_title: Free shipping customizations
|
||||
tags: code-snippets
|
||||
current wccom url: https://woocommerce.com/document/free-shipping/#advanced-settings-customization
|
||||
combined with: https://woocommerce.com/document/hide-other-shipping-methods-when-free-shipping-is-available/#use-a-plugin
|
||||
---
|
||||
|
||||
## Free Shipping: Advanced Settings/Customization
|
||||
|
||||
### Overview
|
||||
|
||||
By default, WooCommerce shows all shipping methods that match the customer and the cart contents. This means Free Shipping also shows along with Flat Rate and other Shipping Methods.
|
||||
|
||||
The functionality to hide all other methods, and only show Free Shipping, requires either custom PHP code or a plugin/extension.
|
||||
|
||||
### Adding code
|
||||
|
||||
Before adding snippets, clear your WooCommerce cache. Go to WooCommerce > System Status > Tools > WooCommerce Transients > Clear transients.
|
||||
|
||||
Add this code to your child theme’s `functions.php`, or via a plugin that allows custom functions to be added. Please don’t add custom code directly to a parent theme’s `functions.php` as changes are entirely erased when a parent theme updates.
|
||||
|
||||
## Code Snippets
|
||||
|
||||
### Enabling or Disabling Free Shipping via Hooks
|
||||
|
||||
You can hook into the `is_available` function of the free shipping method.
|
||||
|
||||
```php
|
||||
return apply_filters( 'woocommerce_shipping_' . $this->id . '_is_available', $is_available );
|
||||
```
|
||||
|
||||
This means you can use `add_filter()` on `woocommerce_shipping_free_shipping_is_available` and return `true` or `false`.
|
||||
|
||||
### How do I only show Free Shipping?
|
||||
|
||||
The following snippet hides everything but `free_shipping`, if it’s available and the customer's cart qualifies.
|
||||
|
||||
```php
|
||||
/**
|
||||
* Hide shipping rates when free shipping is available.
|
||||
* Updated to support WooCommerce 2.6 Shipping Zones.
|
||||
*
|
||||
* @param array $rates Array of rates found for the package.
|
||||
* @return array
|
||||
*/
|
||||
function my_hide_shipping_when_free_is_available( $rates ) {
|
||||
$free = array();
|
||||
foreach ( $rates as $rate_id => $rate ) {
|
||||
if ( 'free_shipping' === $rate->method_id ) {
|
||||
$free[ $rate_id ] = $rate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ! empty( $free ) ? $free : $rates;
|
||||
}
|
||||
add_filter( 'woocommerce_package_rates', 'my_hide_shipping_when_free_is_available', 100 );
|
||||
```
|
||||
|
||||
### How do I only show Local Pickup and Free Shipping?
|
||||
|
||||
The snippet below hides everything but `free_shipping` and `local_pickup`, if it’s available and the customer's cart qualifies.
|
||||
|
||||
```php
|
||||
|
||||
/**
|
||||
* Hide shipping rates when free shipping is available, but keep "Local pickup"
|
||||
* Updated to support WooCommerce 2.6 Shipping Zones
|
||||
*/
|
||||
|
||||
function hide_shipping_when_free_is_available( $rates, $package ) {
|
||||
$new_rates = array();
|
||||
foreach ( $rates as $rate_id => $rate ) {
|
||||
// Only modify rates if free_shipping is present.
|
||||
if ( 'free_shipping' === $rate->method_id ) {
|
||||
$new_rates[ $rate_id ] = $rate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $new_rates ) ) {
|
||||
//Save local pickup if it's present.
|
||||
foreach ( $rates as $rate_id => $rate ) {
|
||||
if ('local_pickup' === $rate->method_id ) {
|
||||
$new_rates[ $rate_id ] = $rate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $new_rates;
|
||||
}
|
||||
|
||||
return $rates;
|
||||
}
|
||||
|
||||
add_filter( 'woocommerce_package_rates', 'hide_shipping_when_free_is_available', 10, 2 );
|
||||
```
|
||||
|
||||
### Only show free shipping in all states except…
|
||||
|
||||
This snippet results in showing only free shipping in all states except the exclusion list. It hides free shipping if the customer is in one of the states listed:
|
||||
|
||||
```php
|
||||
/**
|
||||
* Hide ALL shipping options when free shipping is available and customer is NOT in certain states
|
||||
*
|
||||
* Change $excluded_states = array( 'AK','HI','GU','PR' ); to include all the states that DO NOT have free shipping
|
||||
*/
|
||||
add_filter( 'woocommerce_package_rates', 'hide_all_shipping_when_free_is_available' , 10, 2 );
|
||||
|
||||
/**
|
||||
* Hide ALL Shipping option when free shipping is available
|
||||
*
|
||||
* @param array $available_methods
|
||||
*/
|
||||
function hide_all_shipping_when_free_is_available( $rates, $package ) {
|
||||
|
||||
$excluded_states = array( 'AK','HI','GU','PR' );
|
||||
if( isset( $rates['free_shipping'] ) AND !in_array( WC()->customer->shipping_state, $excluded_states ) ) :
|
||||
// Get Free Shipping array into a new array
|
||||
$freeshipping = array();
|
||||
$freeshipping = $rates['free_shipping'];
|
||||
|
||||
// Empty the $available_methods array
|
||||
unset( $rates );
|
||||
|
||||
// Add Free Shipping back into $avaialble_methods
|
||||
$rates = array();
|
||||
$rates[] = $freeshipping;
|
||||
|
||||
endif;
|
||||
|
||||
if( isset( $rates['free_shipping'] ) AND in_array( WC()->customer->shipping_state, $excluded_states ) ) {
|
||||
|
||||
// remove free shipping option
|
||||
unset( $rates['free_shipping'] );
|
||||
|
||||
}
|
||||
|
||||
return $rates;
|
||||
}
|
||||
```
|
||||
|
||||
### Enable Shipping Methods on a per Class / Product Basis, split orders, or other scenarios?
|
||||
|
||||
Need more flexibility? Take a look at our [premium Shipping Method extensions](https://woocommerce.com/product-category/woocommerce-extensions/shipping-methods/).
|
||||
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
---
|
||||
post_title: Legacy Local Pickup Advanced Settings and Customization
|
||||
tags: code-snippet
|
||||
current wccom url: https://woocommerce.com/document/local-pickup/#advanced-settings-customization
|
||||
note: Docs links out to Skyverge's site for howto add a custom email - do we have our own alternative?
|
||||
---
|
||||
|
||||
# Advanced settings and customization for legacy Local Pickup
|
||||
|
||||
## Disable local taxes when using local pickup
|
||||
|
||||
Local Pickup calculates taxes based on your store’s location (address) by default, and not the customer’s address. Add this snippet at the end of your theme's `functions.php` to use your standard tax configuration instead:
|
||||
|
||||
```php
|
||||
add_filter( 'woocommerce_apply_base_tax_for_local_pickup', '__return_false' );
|
||||
```
|
||||
|
||||
Regular taxes is then used when local pickup is selected, instead of store-location-based taxes.
|
||||
|
||||
## Changing the location for local taxes
|
||||
|
||||
To charge local taxes based on the postcode and city of the local pickup location, you need to define the shop’s base city and post code using this example code:
|
||||
|
||||
```php
|
||||
add_filter( 'woocommerce_countries_base_postcode', create_function( '', 'return "80903";' ) );
|
||||
add_filter( 'woocommerce_countries_base_city', create_function( '', 'return "COLORADO SPRINGS";' ) );
|
||||
```
|
||||
|
||||
Update `80903` to reflect your preferred postcode/zip, and `COLORADO SPRINGS` with your preferred town or city.
|
||||
|
||||
## Custom emails for local pickup
|
||||
|
||||
_Shipping Address_ is not displayed on the admin order emails when Local Pickup is used as the shipping method.
|
||||
|
||||
Since all core shipping options use the standard order flow, customers receive the same order confirmation email whether they select local pickup or any other shipping option.
|
||||
Use this guide to create custom emails for local pickup if you’d like to send a separate email for local pickup orders: [How to Add a Custom WooCommerce Email](https://www.skyverge.com/blog/how-to-add-a-custom-woocommerce-email/).
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
post_title: Making your translation upgrade safe
|
||||
menu_title: Translation upgrade safety
|
||||
tags: code-snippet
|
||||
current wccom url: https://woocommerce.com/document/woocommerce-localization/#making-your-translation-upgrade-safe
|
||||
---
|
||||
|
||||
# Making your translation upgrade safe
|
||||
|
||||
Like all other plugins, WooCommerce keeps translations in `wp-content/languages/plugins`.
|
||||
|
||||
However, if you want to include a custom translation, you can add them to `wp-content/languages/woocommerce`, or you can use a snippet to load a custom translation stored elsewhere:
|
||||
|
||||
```php
|
||||
// Code to be placed in functions.php of your theme or a custom plugin file.
|
||||
add_filter( 'load_textdomain_mofile', 'load_custom_plugin_translation_file', 10, 2 );
|
||||
|
||||
/*
|
||||
* Replace 'textdomain' with your plugin's textdomain. e.g. 'woocommerce'.
|
||||
* File to be named, for example, yourtranslationfile-en_GB.mo
|
||||
* File to be placed, for example, wp-content/lanaguages/textdomain/yourtranslationfile-en_GB.mo
|
||||
*/
|
||||
function load_custom_plugin_translation_file( $mofile, $domain ) {
|
||||
if ( 'textdomain' === $domain ) {
|
||||
$mofile = WP_LANG_DIR . '/textdomain/yourtranslationfile-' . get_locale() . '.mo';
|
||||
}
|
||||
return $mofile;
|
||||
}
|
||||
```
|
|
@ -0,0 +1,210 @@
|
|||
---
|
||||
post_title: Shipping Method API
|
||||
menu_title: Shipping method API
|
||||
tags: shipping, API
|
||||
current wccom url: https://woocommerce.com/document/shipping-method-api/
|
||||
---
|
||||
|
||||
|
||||
# Shipping Method API
|
||||
|
||||
WooCommerce has a shipping method API which plugins can use to add their own rates. This article outlines steps to create a new shipping method and interact with the API.
|
||||
|
||||
## Create a plugin
|
||||
|
||||
First, create a regular WordPress/WooCommerce plugin (see [Create a plugin](https://woocommerce.com/document/create-a-plugin/)). You’ll define your shipping method class in this plugin file and maintain it outside of WooCommerce.
|
||||
|
||||
## Create a function to house your class
|
||||
|
||||
To ensure the classes you need to extend exist, you should wrap your class in a function which is called after all plugins are loaded:
|
||||
|
||||
```php
|
||||
function your_shipping_method_init() {
|
||||
// Your class will go here
|
||||
}
|
||||
|
||||
add_action( 'woocommerce_shipping_init', 'your_shipping_method_init' );
|
||||
```
|
||||
|
||||
## Create your class
|
||||
|
||||
Create your class and place it inside the function you just created. Make sure it extends the shipping method class so that you have access to the API. You’ll see below we also init our shipping method options.
|
||||
|
||||
```php
|
||||
if ( ! class_exists( 'WC_Your_Shipping_Method' ) ) {
|
||||
class WC_Your_Shipping_Method extends WC_Shipping_Method {
|
||||
/**
|
||||
* Constructor for your shipping class
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->id = 'your_shipping_method';
|
||||
$this->title = __( 'Your Shipping Method' );
|
||||
$this->method_description = __( 'Description of your shipping method' ); //
|
||||
$this->enabled = "yes"; // This can be added as an setting but for this example its forced enabled
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init your settings
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
function init() {
|
||||
// Load the settings API
|
||||
$this->init_form_fields(); // This is part of the settings API. Override the method to add your own settings
|
||||
$this->init_settings(); // This is part of the settings API. Loads settings you previously init.
|
||||
|
||||
// Save settings in admin if you have any defined
|
||||
add_action( 'woocommerce_update_options_shipping_' . $this->id, array( $this, 'process_admin_options' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* calculate_shipping function.
|
||||
*
|
||||
* @access public
|
||||
* @param mixed $package
|
||||
* @return void
|
||||
*/
|
||||
public function calculate_shipping( $package ) {
|
||||
// This is where you'll add your rates
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
As well as declaring your class, you also need to tell WooCommerce it exists with another function:
|
||||
|
||||
```php
|
||||
function add_your_shipping_method( $methods ) {
|
||||
$methods['your_shipping_method'] = 'WC_Your_Shipping_Method';
|
||||
return $methods;
|
||||
}
|
||||
|
||||
add_filter( 'woocommerce_shipping_methods', 'add_your_shipping_method' );
|
||||
```
|
||||
|
||||
## Defining settings/options
|
||||
|
||||
You can define your options once the above is in place by using the settings API. In the snippets above you’ll notice we `init_form_fields` and `init_settings`. These load up the settings API. To see how to add settings, see [WooCommerce settings API](https://woocommerce.com/document/settings-api/).
|
||||
|
||||
## The calculate_shipping() method
|
||||
|
||||
Add your rates by usign the `calculate_shipping()` method. WooCommerce calls this when doing shipping calculations. Do your plugin specific calculations here and then add the rates via the API. Like so:
|
||||
|
||||
```php
|
||||
$rate = array(
|
||||
'label' => "Label for the rate",
|
||||
'cost' => '10.99',
|
||||
'calc_tax' => 'per_item'
|
||||
);
|
||||
|
||||
// Register the rate
|
||||
$this->add_rate( $rate );
|
||||
```
|
||||
|
||||
`Add_rate` takes an array of options. The defaults/possible values for the array are as follows:
|
||||
|
||||
```php
|
||||
$defaults = array(
|
||||
'label' => '', // Label for the rate
|
||||
'cost' => '0', // Amount for shipping or an array of costs (for per item shipping)
|
||||
'taxes' => '', // Pass an array of taxes, or pass nothing to have it calculated for you, or pass 'false' to calculate no tax for this method
|
||||
'calc_tax' => 'per_order' // Calc tax per_order or per_item. Per item needs an array of costs passed via 'cost'
|
||||
);
|
||||
```
|
||||
|
||||
Your shipping method can pass as many rates as you want – just ensure that the id for each is different. The user will get to choose rate during checkout.
|
||||
|
||||
|
||||
## Piecing it all together
|
||||
|
||||
The skeleton shipping method code all put together looks like this:
|
||||
|
||||
```php
|
||||
<?php
|
||||
/*
|
||||
Plugin Name: Your Shipping plugin
|
||||
Plugin URI: https://woocommerce.com/
|
||||
Description: Your shipping method plugin
|
||||
Version: 1.0.0
|
||||
Author: WooThemes
|
||||
Author URI: https://woocommerce.com/
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check if WooCommerce is active
|
||||
*/
|
||||
if ( in_array( 'woocommerce/woocommerce.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) {
|
||||
|
||||
function your_shipping_method_init() {
|
||||
if ( ! class_exists( 'WC_Your_Shipping_Method' ) ) {
|
||||
class WC_Your_Shipping_Method extends WC_Shipping_Method {
|
||||
/**
|
||||
* Constructor for your shipping class
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->id = 'your_shipping_method'; // Id for your shipping method. Should be uunique.
|
||||
$this->method_title = __( 'Your Shipping Method' ); // Title shown in admin
|
||||
$this->method_description = __( 'Description of your shipping method' ); // Description shown in admin
|
||||
|
||||
$this->enabled = "yes"; // This can be added as an setting but for this example its forced enabled
|
||||
$this->title = "My Shipping Method"; // This can be added as an setting but for this example its forced.
|
||||
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init your settings
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
function init() {
|
||||
// Load the settings API
|
||||
$this->init_form_fields(); // This is part of the settings API. Override the method to add your own settings
|
||||
$this->init_settings(); // This is part of the settings API. Loads settings you previously init.
|
||||
|
||||
// Save settings in admin if you have any defined
|
||||
add_action( 'woocommerce_update_options_shipping_' . $this->id, array( $this, 'process_admin_options' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* calculate_shipping function.
|
||||
*
|
||||
* @access public
|
||||
* @param array $package
|
||||
* @return void
|
||||
*/
|
||||
public function calculate_shipping( $package = array() ) {
|
||||
$rate = array(
|
||||
'label' => $this->title,
|
||||
'cost' => '10.99',
|
||||
'calc_tax' => 'per_item'
|
||||
);
|
||||
|
||||
// Register the rate
|
||||
$this->add_rate( $rate );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
add_action( 'woocommerce_shipping_init', 'your_shipping_method_init' );
|
||||
|
||||
function add_your_shipping_method( $methods ) {
|
||||
$methods['your_shipping_method'] = 'WC_Your_Shipping_Method';
|
||||
return $methods;
|
||||
}
|
||||
|
||||
add_filter( 'woocommerce_shipping_methods', 'add_your_shipping_method' );
|
||||
}
|
||||
```
|
||||
|
||||
For further information, please check out the [Shipping Method API Wiki](https://github.com/woocommerce/woocommerce/wiki/Shipping-Method-API).
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
post_title: SSL and HTTPS and WooCommerce
|
||||
menu_title: SSL and HTTPS and WooCommerce
|
||||
tags: code-snippet
|
||||
current wccom url: https://woocommerce.com/document/ssl-and-https/#websites-behind-load-balancers-or-reverse-proxies
|
||||
---
|
||||
|
||||
## Websites behind load balancers or reverse proxies
|
||||
|
||||
WooCommerce uses the `is_ssl()` WordPress function to verify if your website using SSL or not.
|
||||
|
||||
`is_ssl()` checks if the connection is via HTTPS or on Port 443. However, this won’t work for websites behind load balancers, especially websites hosted at Network Solutions. For details, read [WordPress is_ssl() function reference notes](https://codex.wordpress.org/Function_Reference/is_ssl#Notes).
|
||||
|
||||
Websites behind load balancers or reverse proxies that support `HTTP_X_FORWARDED_PROTO` can be fixed by adding the following code to the `wp-config.php` file, above the require_once call:
|
||||
|
||||
```php
|
||||
if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && 'https' == $_SERVER['HTTP_X_FORWARDED_PROTO'] ) {
|
||||
$_SERVER['HTTPS'] = 'on';
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** If you use CloudFlare, you need to configure it. Check their documentation.
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
post_title: Uninstall and remove all WooCommerce Data
|
||||
menu_title: Uninstalling and removing data
|
||||
tags: code-snippet
|
||||
current wccom url: https://woocommerce.com/document/installing-uninstalling-woocommerce/#uninstalling-woocommerce
|
||||
---
|
||||
|
||||
# Uninstall and remove all WooCommerce Data
|
||||
|
||||
The WooCommerce plugin can be uninstalled like any other WordPress plugin. By default, the WooCommerce data is left in place though.
|
||||
|
||||
If you need to remove *all* WooCommerce data as well, including products, order data, coupons, etc., you need to to modify the site’s `wp-config.php` *before* deactivating and deleting the WooCommerce plugin.
|
||||
|
||||
As this action is destructive and permanent, the information is provided as is. WooCommerce Support cannot help with this process or anything that happens as a result.
|
||||
|
||||
To fully remove all WooCommerce data from your WordPress site, open `wp-config.php`, scroll down to the bottom of the file, and add the following constant on its own line above `/* That’s all, stop editing. */`.
|
||||
|
||||
```php
|
||||
define( 'WC_REMOVE_ALL_DATA', true );
|
||||
|
||||
/* That’s all, stop editing! Happy publishing. */
|
||||
```
|
||||
|
||||
Then, once the changes are saved to the file, when you deactivate and delete WooCommerce, all of its data is removed from your WordPress site database.
|
||||
|
||||
![Uninstall WooCommerce WPConfig](https://woocommerce.com/wp-content/uploads/2020/03/uninstall_wocommerce_plugin_wpconfig.png)
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
post_title: Using NGINX server to protect your upload directory
|
||||
menu_title: NGINX server to protect upload directory
|
||||
tags: code-snippet
|
||||
current wccom url: https://woocommerce.com/document/digital-downloadable-product-handling/#protecting-your-uploads-directory
|
||||
---
|
||||
|
||||
## Using NGINX server to protect your upload directory
|
||||
|
||||
If you using NGINX server for your site along with **X-Accel-Redirect/X-Sendfile** or **Force Downloads** download method, it is necessary that you add this configuration for better security:
|
||||
|
||||
```php
|
||||
# Protect WooCommerce upload folder from being accessed directly.
|
||||
# You may want to change this config if you are using "X-Accel-Redirect/X-Sendfile" or "Force Downloads" method for downloadable products.
|
||||
# Place this config towards the end of "server" block in NGINX configuration.
|
||||
location ~* /wp-content/uploads/woocommerce_uploads/ {
|
||||
if ( $upstream_http_x_accel_redirect = "" ) {
|
||||
return 403;
|
||||
}
|
||||
internal;
|
||||
}
|
||||
```
|
||||
|
||||
And this the configuration in case you are using **Redirect only** download method:
|
||||
|
||||
```php
|
||||
# Protect WooCommerce upload folder from being accessed directly.
|
||||
# You may want to change this config if you are using "Redirect Only" method for downloadable products.
|
||||
# Place this config towards the end of "server" block in NGINX configuration.
|
||||
location ~* /wp-content/uploads/woocommerce_uploads/ {
|
||||
autoindex off;
|
||||
}
|
||||
```
|
||||
|
||||
If you do not know which web server you are using, please reach out to your host along with a link to this support page.
|
|
@ -52,6 +52,15 @@
|
|||
"category_slug": "code-snippets",
|
||||
"category_title": "Code Snippets",
|
||||
"posts": [
|
||||
{
|
||||
"post_title": "Using NGINX server to protect your upload directory",
|
||||
"menu_title": "NGINX server to protect upload directory",
|
||||
"tags": "code-snippet",
|
||||
"edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/code-snippets/using_nginx_server_to_protect_your_uploads_directory.md",
|
||||
"hash": "5d7afe5c8217c3a5f753eb2f468b8304f7f9b5b1275461abf2146e4de82ed6b2",
|
||||
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/code-snippets/using_nginx_server_to_protect_your_uploads_directory.md",
|
||||
"id": "8b325d3483f9a8d09961ca1082839752137faebf"
|
||||
},
|
||||
{
|
||||
"post_title": "Useful core functions",
|
||||
"tags": "code-snippet",
|
||||
|
@ -60,6 +69,15 @@
|
|||
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/code-snippets/useful-functions.md",
|
||||
"id": "0d99f1dee7c104b5899fd62b96157fb6709ebfb8"
|
||||
},
|
||||
{
|
||||
"post_title": "Uninstall and remove all WooCommerce Data",
|
||||
"menu_title": "Uninstalling and removing data",
|
||||
"tags": "code-snippet",
|
||||
"edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/code-snippets/uninstall_remove_all_woocommerce_data.md",
|
||||
"hash": "73483ff158ceac81685a9cd52335dc98e99ac7f84d89cdbcf4ce994e18afe30d",
|
||||
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/code-snippets/uninstall_remove_all_woocommerce_data.md",
|
||||
"id": "36b571fcf2471737729ab4769e2c721b2248187f"
|
||||
},
|
||||
{
|
||||
"post_title": "Unhook and remove WooCommerce emails",
|
||||
"tags": "code-snippet",
|
||||
|
@ -68,6 +86,24 @@
|
|||
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/code-snippets/unhook--remove-woocommerce-emails.md",
|
||||
"id": "0fdfe3b483ae74a9e5dc1fc21b80814462222ec3"
|
||||
},
|
||||
{
|
||||
"post_title": "SSL and HTTPS and WooCommerce",
|
||||
"menu_title": "SSL and HTTPS and WooCommerce",
|
||||
"tags": "code-snippet",
|
||||
"edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/code-snippets/ssl_and_https_and_woocommerce_websites_behind_load_balanacers_or_reverse_proxies.md",
|
||||
"hash": "92a5091c27d1af6c0b49df143dd13886fb2cb30538fa877f68000cab69f4f502",
|
||||
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/code-snippets/ssl_and_https_and_woocommerce_websites_behind_load_balanacers_or_reverse_proxies.md",
|
||||
"id": "78d5b5a20ce6471b74f809386eff41fffe2d1adb"
|
||||
},
|
||||
{
|
||||
"post_title": "Shipping Method API",
|
||||
"menu_title": "Shipping method API",
|
||||
"tags": "shipping, API",
|
||||
"edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/code-snippets/shipping_method_api.md",
|
||||
"hash": "bd7cbc361fe94acaa40fbc5befa8d14f302705a9d700dd7d7e78a482b003fe0b",
|
||||
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/code-snippets/shipping_method_api.md",
|
||||
"id": "a419b97e5594918a015c61227ad9226c509eb314"
|
||||
},
|
||||
{
|
||||
"post_title": "Rename a country",
|
||||
"tags": "code-snippet",
|
||||
|
@ -84,6 +120,15 @@
|
|||
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/code-snippets/number-of-products-per-row.md",
|
||||
"id": "7369dc328c49206771a2f8d0da5d920c480b5207"
|
||||
},
|
||||
{
|
||||
"post_title": "Making your translation upgrade safe",
|
||||
"menu_title": "Translation upgrade safety",
|
||||
"tags": "code-snippet",
|
||||
"edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/code-snippets/making_translations_upgrade_safe.md",
|
||||
"hash": "e2d296630d7af888a072de51870f3b4ff311b3c29f706fda735bd8f9122c8710",
|
||||
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/code-snippets/making_translations_upgrade_safe.md",
|
||||
"id": "0c1add87ef9f5452b4c8404bb55021ad8265c171"
|
||||
},
|
||||
{
|
||||
"post_title": "Add link to logged data",
|
||||
"menu_title": "Add link to logged data",
|
||||
|
@ -93,6 +138,40 @@
|
|||
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/code-snippets/link-to-logged-data.md",
|
||||
"id": "34da337f79be5ce857024f541a99d302174ca37d"
|
||||
},
|
||||
{
|
||||
"post_title": "Legacy Local Pickup Advanced Settings and Customization",
|
||||
"tags": "code-snippet",
|
||||
"edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/code-snippets/legacy_local_pickup_advacned_settings_and_customization.md",
|
||||
"hash": "d0269f1ee2700356672a032e4e54491666b901765045f7c5224ef07eeb9d9598",
|
||||
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/code-snippets/legacy_local_pickup_advacned_settings_and_customization.md",
|
||||
"id": "c4d4a2276fc251082a80a8330eea1eb62a97c3bb"
|
||||
},
|
||||
{
|
||||
"post_title": "Free Shipping Customizations",
|
||||
"menu_title": "Free shipping customizations",
|
||||
"tags": "code-snippets",
|
||||
"edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/code-snippets/free_shipping_customization.md",
|
||||
"hash": "c18884a45e4e1cc7b174820c2553d2722df95b98f6783c2700096a5b7e19bffd",
|
||||
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/code-snippets/free_shipping_customization.md",
|
||||
"id": "cac6f1ccd661588e9a5fa7405643e9c6d4da388e"
|
||||
},
|
||||
{
|
||||
"post_title": "Displaying Custom Fields in Your Theme or Site",
|
||||
"menu_title": "Displaying custom fields in theme",
|
||||
"tags": "code-snippet",
|
||||
"edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/code-snippets/displaying_custom_fields_in_your_theme_or_site.md",
|
||||
"hash": "8048c2e9e5d25268d17d4f4ca7929e265eddbd4653318dd8f544856ddecd39dd",
|
||||
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/code-snippets/displaying_custom_fields_in_your_theme_or_site.md",
|
||||
"id": "3e3fd004afda355cf9dbb05f0967523d6d0da1ce"
|
||||
},
|
||||
{
|
||||
"post_title": "Disabling Marketplace Suggestions Programmatically",
|
||||
"menu_title": "Disabling marketplace suggestions",
|
||||
"edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/code-snippets/disabling_marketplace_suggestions_programmatically.md",
|
||||
"hash": "3d5bd50d64a46efaea99efb0a87dfdb8882cb83598b7be8a8154ad0e464eb6f5",
|
||||
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/code-snippets/disabling_marketplace_suggestions_programmatically.md",
|
||||
"id": "94a7a28e5dd3d9394650e66abec2429445e87028"
|
||||
},
|
||||
{
|
||||
"post_title": "Customizing checkout fields using actions and filters",
|
||||
"tags": "code-snippet",
|
||||
|
@ -101,6 +180,24 @@
|
|||
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/code-snippets/customising-checkout-fields.md",
|
||||
"id": "83097d3b7414557fc80dcf9f8f1a708bbdcdd884"
|
||||
},
|
||||
{
|
||||
"post_title": "Code snippets for configuring special tax scenarios",
|
||||
"menu_title": "Configuring special tax scenarios",
|
||||
"tags": "code-snippet, tax",
|
||||
"edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/code-snippets/configuring_special_tax_scenarios.md",
|
||||
"hash": "128193e0e980f484f354c93e59d34c3948f112e4a1c99158cf3e5d9969db9352",
|
||||
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/code-snippets/configuring_special_tax_scenarios.md",
|
||||
"id": "a8ab8b6734ba2ac5af7c6653635d15548abdab2a"
|
||||
},
|
||||
{
|
||||
"post_title": "Check if a Payment Method Support Refunds, Subscriptions or Pre-orders",
|
||||
"menu_title": "Payment method support for refunds, subscriptions, pre-orders",
|
||||
"tags": "payment-methods",
|
||||
"edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/code-snippets/check_payment_method_support.md",
|
||||
"hash": "6cae4b1fda5980c327c99d6bae8b1978fd05849f07179f0699a174b57d27b862",
|
||||
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/code-snippets/check_payment_method_support.md",
|
||||
"id": "2919c9fc523bce46f43a5f35f821d0c6623c5ede"
|
||||
},
|
||||
{
|
||||
"post_title": "Change a currency symbol",
|
||||
"tags": "code-snippet",
|
||||
|
@ -350,7 +447,7 @@
|
|||
{
|
||||
"post_title": "Logging in WooCommerce",
|
||||
"edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/extension-development/logging.md",
|
||||
"hash": "844689b6d9c482fb217a512db6ddab0afd4d76b2b9e378a9302681d2a8dfe517",
|
||||
"hash": "7e66b9ea605944c5926cf6099fb8fb323976c014fef7dd768c91cef17b091edd",
|
||||
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/extension-development/logging.md",
|
||||
"id": "c684e2efba45051a4e1f98eb5e6ef6bab194f25c"
|
||||
},
|
||||
|
@ -716,7 +813,7 @@
|
|||
"post_title": "Product editor development handbook",
|
||||
"menu_title": "Development handbook",
|
||||
"edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/product-editor-development/product-editor.md",
|
||||
"hash": "cc5f82b66e949e3df2928b5e6b1217e8804c43b8e7b75ebc930cd0f90aef7bbe",
|
||||
"hash": "b574a4a5476899342cd229033a22ecdf9859914ea34446f8276e2b0ad5cb8c7f",
|
||||
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/product-editor-development/product-editor.md",
|
||||
"id": "59450404de2750d918137e7cf523e52bedfd7214",
|
||||
"links": {
|
||||
|
@ -1367,5 +1464,5 @@
|
|||
"categories": []
|
||||
}
|
||||
],
|
||||
"hash": "cf89b5c07e5b7a9007eb4afe021b02ddce4611c81a863b0a431ae57491a3b37f"
|
||||
"hash": "d8fe058ebcce4d1f40585f1634b1e98e27063d81dd0dd7783217ab60d0bc915a"
|
||||
}
|
|
@ -140,9 +140,9 @@ wc_get_logger()->info(
|
|||
|
||||
### Best practices
|
||||
|
||||
* Rather than using the `WC_Logger`‘s `log()` method directly, it's better to use one of the wrapper methods that's specific to the log level. E.g. `info()` or `error()`.
|
||||
* Rather than using the `WC_Logger`'s `log()` method directly, it's better to use one of the wrapper methods that's specific to the log level. E.g. `info()` or `error()`.
|
||||
* Write a message that is a complete, coherent sentence. This will make it more useful for people who aren't familiar with the codebase.
|
||||
* Log messages should not be translatable (see the discussion about this in the comments). Keeping the message in English makes it easier to search for solutions based on the message contents, and also makes it easier for Happiness Engineers to understand what's happening, since they may not speak the same language as the site owner.
|
||||
* Log messages should not be translatable. Keeping the message in English makes it easier to search for solutions based on the message contents, and also makes it easier for anyone troubleshooting to understand what's happening, since they may not speak the same language as the site owner.
|
||||
* Ideally, each log entry message should be a single line (i.e. no line breaks within the message string). Additional lines or extra data should be put in the context array.
|
||||
* Avoid outputting structured data in the message string. Put it in a key in the context array instead. The logger will handle converting it to JSON and making it legible in the log viewer.
|
||||
* If you need to include a stack trace, let the logger generate it for you.
|
||||
|
@ -159,7 +159,7 @@ The `WC_Logger` class can be substituted for another class via the `woocommerce_
|
|||
|
||||
In WooCommerce, a log handler is a PHP class that takes the raw log data and transforms it into a log entry that can be stored or dispatched. WooCommerce ships with four different log handler classes:
|
||||
|
||||
* `Automattic\WooCommerce\Internal\Admin\Logging\LogHandlerFileV2`: This is the default handler, representing the "file system" log storage method. It records log entries to files.
|
||||
* `Automattic\\WooCommerce\\Internal\\Admin\\Logging\\LogHandlerFileV2`: This is the default handler, representing the "file system" log storage method. It records log entries to files.
|
||||
* `WC_Log_Handler_File`: This is the old default handler that also records log entries to files. It may be deprecated in the future, and it is not recommended to use this class or extend it.
|
||||
* `WC_Log_Handler_DB`: This handler represents the "database" log storage method. It records log entries to the database.
|
||||
* `WC_Log_Handler_Email`: This handler does not store log entries, but instead sends them as email messages. Emails are sent to the site admin email address. This handler has [some limitations](https://github.com/woocommerce/woocommerce/blob/fe81a4cf27601473ad5c394a4f0124c785aaa4e6/plugins/woocommerce/includes/log-handlers/class-wc-log-handler-email.php#L15-L27).
|
||||
|
@ -185,6 +185,10 @@ add_filter( 'woocommerce_register_log_handlers', 'my_wc_log_handlers' );
|
|||
|
||||
You may want to create your own log handler class in order to send logs somewhere else, such as a Slack channel or perhaps an InfluxDB instance. Your class must extend the [`WC_Log_Handler`](https://woocommerce.github.io/code-reference/classes/WC-Log-Handler.html) abstract class and implement the [`WC_Log_Handler_Interface`](https://woocommerce.github.io/code-reference/classes/WC-Log-Handler-Interface.html) interface. The [`WC_Log_Handler_Email`](https://github.com/woocommerce/woocommerce/blob/6688c60fe47ad42d49deedab8be971288e4786c1/plugins/woocommerce/includes/log-handlers/class-wc-log-handler-email.php) handler class provides a good example of how to set this up.
|
||||
|
||||
### Log file storage location
|
||||
|
||||
When using the "file system" log handler, by default the log files are stored in the `wc-logs` subdirectory of the WordPress `uploads` directory, which means they might be publicly accessible. WooCommerce adds an `.htaccess` file to prevent access to `wc-logs`, but not all web servers recognize that file. If you have the option, you may want to consider storing your log files in a directory outside of the web root. Make sure the directory has the same user/group permissions as the `uploads` directory so that WordPress can access it. Then use the `woocommerce_log_directory` filter hook to set the path to your custom directory.
|
||||
|
||||
### Turning off noisy logs
|
||||
|
||||
If there is a particular log that is recurring frequently and clogging up your log files, you should probably figure out why it keeps getting triggered and resolve the issue. However, if that's not possible, you can add a callback to the `woocommerce_logger_log_message` filter hook to ignore that particular log while still allowing other logs to get through:
|
||||
|
|
|
@ -39,3 +39,4 @@ Please note that this check is currently not being enforced: the product editor
|
|||
- [Examples on Template API usage](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/ProductTemplates/README.md/)
|
||||
- [Related hooks and Template API documentation](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/src/Admin/BlockTemplates/README.md)
|
||||
- [Generic blocks documentation](https://github.com/woocommerce/woocommerce/blob/trunk/packages/js/product-editor/src/blocks/generic/README.md)
|
||||
- [Validations and error handling](https://github.com/woocommerce/woocommerce/blob/trunk/packages/js/product-editor/src/contexts/validation-context/README.md)
|
||||
|
|
|
@ -22,8 +22,8 @@
|
|||
"test": "pnpm -r test",
|
||||
"lint": "pnpm -r lint",
|
||||
"cherry-pick": "node ./tools/cherry-pick/bin/run",
|
||||
"clean": "rimraf -g '**/node_modules' '**/.wireit' && pnpm store prune && pnpm i",
|
||||
"distclean": "git clean --force -d -X",
|
||||
"clean": "rimraf -g '**/node_modules' '**/.wireit' && pnpm store prune",
|
||||
"buildclean": "git clean --force -d -X ./packages ./plugins ./tools",
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"postinstall": "pnpm git:update-hooks",
|
||||
"git:update-hooks": "if test -d .git; then rm -rf .git/hooks && mkdir -p .git/hooks && husky install; else husky install; fi",
|
||||
|
@ -75,6 +75,9 @@
|
|||
"@types/react": "^17.0.71",
|
||||
"react-resize-aware": "3.1.1",
|
||||
"@automattic/tour-kit>@wordpress/element": "4.4.1"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"@wordpress/edit-site@5.15.0": "bin/patches/@wordpress__edit-site@5.15.0.patch"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
Update pnpm to 9.1.0
|
||||
Update dependencies
|
|
@ -39,10 +39,7 @@ describe( 'Shipping methods API tests', () => {
|
|||
expect( body.method_id ).toEqual( methodId );
|
||||
expect( body.method_title ).toEqual( methodTitle );
|
||||
expect( body.enabled ).toEqual( true );
|
||||
|
||||
if ( [ 'flat_rate', 'local_pickup' ].includes( methodId ) ) {
|
||||
expect( body.settings.cost.value ).toEqual( cost );
|
||||
}
|
||||
expect( body.settings.cost.value || '' ).toEqual( cost || '' );
|
||||
|
||||
// Cleanup: Delete the shipping method
|
||||
await shippingMethodsApi.delete.shippingMethod(
|
||||
|
|
|
@ -10,9 +10,9 @@ features:
|
|||
|
||||
_\* TypeScript Definitions and Repositories are currently only supported for [Products](https://woocommerce.github.io/woocommerce-rest-api-docs/#products), and partially supported for [Orders](https://woocommerce.github.io/woocommerce-rest-api-docs/#orders)._
|
||||
|
||||
## Differences from @woocommerce/woocomerce-rest-api
|
||||
## Differences from @woocommerce/woocommerce-rest-api
|
||||
|
||||
WooCommerce has two API clients in JavaScript for interacting with a WooCommerce installation's RESTful API. This package, and the [@woocommerce/woocomerce-rest-api](https://www.npmjs.com/package/@woocommerce/woocommerce-rest-api) package.
|
||||
WooCommerce has two API clients in JavaScript for interacting with a WooCommerce installation's RESTful API. This package, and the [@woocommerce/woocommerce-rest-api](https://www.npmjs.com/package/@woocommerce/woocommerce-rest-api) package.
|
||||
|
||||
The main difference between them is the Repositories and the TypeScript definitions for the supported endpoints. When using Axios directly, as you can do with both libraries, you query the WooCommerce API in a raw object format, following the [API documentation](https://woocommerce.github.io/woocommerce-rest-api-docs/#introduction) parameters. Comparatively, with the Repositories provided in this package, you have the parameters as properties of an object, which gives you the benefits of auto-complete and strict types, for instance.
|
||||
|
||||
|
@ -104,7 +104,7 @@ The following methods are available on all repositories if the corresponding met
|
|||
- `read( objectId )` - Read a single object of the model type
|
||||
- `update( objectId, {...properties} )` - Update a single object of the model type
|
||||
|
||||
#### Child Repositories
|
||||
#### Child Repositories Use
|
||||
|
||||
In child model repositories, each method requires the `parentId` as the first parameter:
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
Significance: patch
|
||||
Type: tweak
|
||||
Comment: Fix typo (README.md)
|
||||
|
||||
Correct spelling errors
|
|
@ -1,4 +1,4 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
Update pnpm to 9.1.0
|
||||
Update dependencies
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
CI: added a missing dev-dependency for passing tests in updated CI environment.
|
|
@ -0,0 +1,5 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
Comment: Fix missing onEscape handle in SelectTree
|
||||
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: tweak
|
||||
|
||||
Fix typo in experimental select control tests
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: update
|
||||
|
||||
Allow adding HTML to label prop in label component
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: update
|
||||
|
||||
Update SelectTree and Tree controls to allow highlighting items without focus
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: update
|
||||
|
||||
SelectTree: allow navigation between items and input using tab and arrow keys
|
|
@ -171,6 +171,7 @@
|
|||
"jest-cli": "~27.5.1",
|
||||
"postcss": "^8.4.32",
|
||||
"postcss-loader": "^4.3.0",
|
||||
"qrcode.react": "^3.1.0",
|
||||
"react": "^17.0.2",
|
||||
"rimraf": "5.0.5",
|
||||
"sass-loader": "^10.5.0",
|
||||
|
|
|
@ -2,14 +2,23 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
import { createElement } from '@wordpress/element';
|
||||
import {
|
||||
createElement,
|
||||
forwardRef,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
} from '@wordpress/element';
|
||||
import { decodeEntities } from '@wordpress/html-entities';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import Tag from '../tag';
|
||||
import { getItemLabelType, getItemValueType } from './types';
|
||||
import {
|
||||
getItemLabelType,
|
||||
getItemValueType,
|
||||
SelectedItemFocusHandle,
|
||||
} from './types';
|
||||
|
||||
type SelectedItemsProps< ItemType > = {
|
||||
isReadOnly: boolean;
|
||||
|
@ -22,16 +31,23 @@ type SelectedItemsProps< ItemType > = {
|
|||
[ key: string ]: string;
|
||||
};
|
||||
onRemove: ( item: ItemType ) => void;
|
||||
onBlur?: ( event: React.FocusEvent ) => void;
|
||||
onSelectedItemsEnd?: () => void;
|
||||
};
|
||||
|
||||
export const SelectedItems = < ItemType, >( {
|
||||
isReadOnly,
|
||||
items,
|
||||
getItemLabel,
|
||||
getItemValue,
|
||||
getSelectedItemProps,
|
||||
onRemove,
|
||||
}: SelectedItemsProps< ItemType > ) => {
|
||||
const PrivateSelectedItems = < ItemType, >(
|
||||
{
|
||||
isReadOnly,
|
||||
items,
|
||||
getItemLabel,
|
||||
getItemValue,
|
||||
getSelectedItemProps,
|
||||
onRemove,
|
||||
onBlur,
|
||||
onSelectedItemsEnd,
|
||||
}: SelectedItemsProps< ItemType >,
|
||||
ref: React.ForwardedRef< SelectedItemFocusHandle >
|
||||
) => {
|
||||
const classes = classnames(
|
||||
'woocommerce-experimental-select-control__selected-items',
|
||||
{
|
||||
|
@ -39,6 +55,16 @@ export const SelectedItems = < ItemType, >( {
|
|||
}
|
||||
);
|
||||
|
||||
const lastRemoveButtonRef = useRef< HTMLButtonElement >( null );
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => {
|
||||
return () => lastRemoveButtonRef.current?.focus();
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
if ( isReadOnly ) {
|
||||
return (
|
||||
<div className={ classes }>
|
||||
|
@ -51,6 +77,25 @@ export const SelectedItems = < ItemType, >( {
|
|||
);
|
||||
}
|
||||
|
||||
const focusSibling = ( event: React.KeyboardEvent< HTMLDivElement > ) => {
|
||||
const selectedItem = ( event.target as HTMLElement ).closest(
|
||||
'.woocommerce-experimental-select-control__selected-item'
|
||||
);
|
||||
const sibling =
|
||||
event.key === 'ArrowLeft' || event.key === 'Backspace'
|
||||
? selectedItem?.previousSibling
|
||||
: selectedItem?.nextSibling;
|
||||
if ( sibling ) {
|
||||
(
|
||||
( sibling as HTMLElement ).querySelector(
|
||||
'.woocommerce-tag__remove'
|
||||
) as HTMLElement
|
||||
)?.focus();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={ classes }>
|
||||
{ items.map( ( item, index ) => {
|
||||
|
@ -71,6 +116,30 @@ export const SelectedItems = < ItemType, >( {
|
|||
onClick={ ( event ) => {
|
||||
event.preventDefault();
|
||||
} }
|
||||
onKeyDown={ ( event ) => {
|
||||
if (
|
||||
event.key === 'ArrowLeft' ||
|
||||
event.key === 'ArrowRight'
|
||||
) {
|
||||
const focused = focusSibling( event );
|
||||
if (
|
||||
! focused &&
|
||||
event.key === 'ArrowRight' &&
|
||||
onSelectedItemsEnd
|
||||
) {
|
||||
onSelectedItemsEnd();
|
||||
}
|
||||
} else if (
|
||||
event.key === 'ArrowUp' ||
|
||||
event.key === 'ArrowDown'
|
||||
) {
|
||||
event.preventDefault(); // prevent unwanted scroll
|
||||
} else if ( event.key === 'Backspace' ) {
|
||||
onRemove( item );
|
||||
focusSibling( event );
|
||||
}
|
||||
} }
|
||||
onBlur={ onBlur }
|
||||
>
|
||||
{ /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */ }
|
||||
{ /* @ts-ignore Additional props are not required. */ }
|
||||
|
@ -78,6 +147,11 @@ export const SelectedItems = < ItemType, >( {
|
|||
id={ getItemValue( item ) }
|
||||
remove={ () => () => onRemove( item ) }
|
||||
label={ getItemLabel( item ) }
|
||||
ref={
|
||||
index === items.length - 1
|
||||
? lastRemoveButtonRef
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -85,3 +159,9 @@ export const SelectedItems = < ItemType, >( {
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const SelectedItems = forwardRef( PrivateSelectedItems ) as < ItemType >(
|
||||
props: SelectedItemsProps< ItemType > & {
|
||||
ref?: React.ForwardedRef< SelectedItemFocusHandle >;
|
||||
}
|
||||
) => ReturnType< typeof PrivateSelectedItems >;
|
||||
|
|
|
@ -49,7 +49,7 @@ describe( 'useAsyncFilter', () => {
|
|||
expect( filter ).toHaveBeenCalledWith( inputValue );
|
||||
} );
|
||||
|
||||
it( 'should trigger onFilterStart at the begining of the filtering', async () => {
|
||||
it( 'should trigger onFilterStart at the beginning of the filtering', async () => {
|
||||
const filteredItems: string[] = [];
|
||||
|
||||
onFilterStart.mockImplementation( ( value = '' ) => {
|
||||
|
@ -78,7 +78,7 @@ describe( 'useAsyncFilter', () => {
|
|||
expect( filter ).toHaveBeenCalledWith( inputValue );
|
||||
} );
|
||||
|
||||
it( 'should trigger onFilterEnd when filtering is fullfiled', async () => {
|
||||
it( 'should trigger onFilterEnd when filtering is fulfilled', async () => {
|
||||
const filteredItems: string[] = [];
|
||||
|
||||
filter.mockResolvedValue( filteredItems );
|
||||
|
|
|
@ -58,3 +58,5 @@ export type getItemLabelType< ItemType > = ( item: ItemType | null ) => string;
|
|||
export type getItemValueType< ItemType > = (
|
||||
item: ItemType | null
|
||||
) => string | number;
|
||||
|
||||
export type SelectedItemFocusHandle = () => void;
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
useLayoutEffect,
|
||||
useState,
|
||||
} from '@wordpress/element';
|
||||
import { escapeRegExp } from 'lodash';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -26,6 +27,7 @@ type MenuProps = {
|
|||
isLoading?: boolean;
|
||||
position?: Popover.Position;
|
||||
scrollIntoViewOnOpen?: boolean;
|
||||
highlightedIndex?: number;
|
||||
items: LinkedTree[];
|
||||
treeRef?: React.ForwardedRef< HTMLOListElement >;
|
||||
onClose?: () => void;
|
||||
|
@ -44,6 +46,7 @@ export const SelectTreeMenu = ( {
|
|||
onEscape,
|
||||
shouldShowCreateButton,
|
||||
onFirstItemLoop,
|
||||
onExpand,
|
||||
...props
|
||||
}: MenuProps ) => {
|
||||
const [ boundingRect, setBoundingRect ] = useState< DOMRect >();
|
||||
|
@ -66,7 +69,7 @@ export const SelectTreeMenu = ( {
|
|||
// Scroll the selected item into view when the menu opens.
|
||||
useEffect( () => {
|
||||
if ( isOpen && scrollIntoViewOnOpen ) {
|
||||
selectControlMenuRef.current?.scrollIntoView();
|
||||
selectControlMenuRef.current?.scrollIntoView?.();
|
||||
}
|
||||
}, [ isOpen, scrollIntoViewOnOpen ] );
|
||||
|
||||
|
@ -74,9 +77,10 @@ export const SelectTreeMenu = ( {
|
|||
if ( ! props.createValue || ! item.children?.length ) return false;
|
||||
return item.children.some( ( child ) => {
|
||||
if (
|
||||
new RegExp( props.createValue || '', 'ig' ).test(
|
||||
child.data.label
|
||||
)
|
||||
new RegExp(
|
||||
escapeRegExp( props.createValue || '' ),
|
||||
'ig'
|
||||
).test( child.data.label )
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
@ -130,6 +134,7 @@ export const SelectTreeMenu = ( {
|
|||
ref={ ref }
|
||||
items={ items }
|
||||
onTreeBlur={ onClose }
|
||||
onExpand={ onExpand }
|
||||
shouldItemBeExpanded={
|
||||
shouldItemBeExpanded
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
useEffect,
|
||||
useState,
|
||||
Fragment,
|
||||
useRef,
|
||||
} from '@wordpress/element';
|
||||
import { useInstanceId } from '@wordpress/compose';
|
||||
import { BaseControl, Button, TextControl } from '@wordpress/components';
|
||||
|
@ -18,13 +19,23 @@ import { speak } from '@wordpress/a11y';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { useLinkedTree } from '../experimental-tree-control/hooks/use-linked-tree';
|
||||
import { Item, TreeControlProps } from '../experimental-tree-control/types';
|
||||
import {
|
||||
toggleNode,
|
||||
createLinkedTree,
|
||||
getVisibleNodeIndex as getVisibleNodeIndex,
|
||||
getNodeDataByIndex,
|
||||
} from '../experimental-tree-control/linked-tree-utils';
|
||||
import {
|
||||
Item,
|
||||
LinkedTree,
|
||||
TreeControlProps,
|
||||
} from '../experimental-tree-control/types';
|
||||
import { SelectedItems } from '../experimental-select-control/selected-items';
|
||||
import { ComboBox } from '../experimental-select-control/combo-box';
|
||||
import { SuffixIcon } from '../experimental-select-control/suffix-icon';
|
||||
import { SelectTreeMenu } from './select-tree-menu';
|
||||
import { escapeHTML } from '../utils';
|
||||
import { SelectedItemFocusHandle } from '../experimental-select-control/types';
|
||||
|
||||
interface SelectTreeProps extends TreeControlProps {
|
||||
id: string;
|
||||
|
@ -48,12 +59,22 @@ export const SelectTree = function SelectTree( {
|
|||
initialInputValue,
|
||||
onInputChange,
|
||||
shouldShowCreateButton,
|
||||
help = __( 'Separate with commas or the Enter key.', 'woocommerce' ),
|
||||
help,
|
||||
isClearingAllowed = false,
|
||||
onClear = () => {},
|
||||
...props
|
||||
}: SelectTreeProps ) {
|
||||
const linkedTree = useLinkedTree( items );
|
||||
const [ linkedTree, setLinkedTree ] = useState< LinkedTree[] >( [] );
|
||||
const [ highlightedIndex, setHighlightedIndex ] = useState( -1 );
|
||||
|
||||
// whenever the items change, the linked tree needs to be recalculated
|
||||
useEffect( () => {
|
||||
setLinkedTree( createLinkedTree( items, props.createValue ) );
|
||||
}, [ items.length ] );
|
||||
|
||||
// reset highlighted index when the input value changes
|
||||
useEffect( () => setHighlightedIndex( -1 ), [ props.createValue ] );
|
||||
|
||||
const selectTreeInstanceId = useInstanceId(
|
||||
SelectTree,
|
||||
'woocommerce-experimental-select-tree-control__dropdown'
|
||||
|
@ -63,6 +84,8 @@ export const SelectTree = function SelectTree( {
|
|||
'woocommerce-select-tree-control__menu'
|
||||
) as string;
|
||||
|
||||
const selectedItemsFocusHandle = useRef< SelectedItemFocusHandle >( null );
|
||||
|
||||
function isEventOutside( event: React.FocusEvent ) {
|
||||
const isInsideSelect = document
|
||||
.getElementById( selectTreeInstanceId )
|
||||
|
@ -74,7 +97,10 @@ export const SelectTree = function SelectTree( {
|
|||
'.woocommerce-experimental-select-tree-control__popover-menu'
|
||||
)
|
||||
?.contains( event.relatedTarget );
|
||||
return ! ( isInsideSelect || isInsidePopover );
|
||||
const isInRemoveTag = event.relatedTarget?.classList.contains(
|
||||
'woocommerce-tag__remove'
|
||||
);
|
||||
return ! isInsideSelect && ! isInRemoveTag && ! isInsidePopover;
|
||||
}
|
||||
|
||||
const recalculateInputValue = () => {
|
||||
|
@ -104,6 +130,19 @@ export const SelectTree = function SelectTree( {
|
|||
}
|
||||
}, [ isFocused ] );
|
||||
|
||||
// Scroll the newly highlighted item into view
|
||||
useEffect(
|
||||
() =>
|
||||
document
|
||||
.querySelector(
|
||||
'.experimental-woocommerce-tree-item--highlighted'
|
||||
)
|
||||
?.scrollIntoView?.( {
|
||||
block: 'nearest',
|
||||
} ),
|
||||
[ highlightedIndex ]
|
||||
);
|
||||
|
||||
let placeholder: string | undefined = '';
|
||||
if ( Array.isArray( props.selected ) ) {
|
||||
placeholder = props.selected.length === 0 ? props.placeholder : '';
|
||||
|
@ -111,12 +150,30 @@ export const SelectTree = function SelectTree( {
|
|||
placeholder = props.placeholder;
|
||||
}
|
||||
|
||||
// reset highlighted index when the input value changes
|
||||
useEffect( () => {
|
||||
if (
|
||||
highlightedIndex === items.length &&
|
||||
! shouldShowCreateButton?.( props.createValue )
|
||||
) {
|
||||
setHighlightedIndex( items.length - 1 );
|
||||
}
|
||||
}, [ props.createValue ] );
|
||||
|
||||
const inputProps: React.InputHTMLAttributes< HTMLInputElement > = {
|
||||
className: 'woocommerce-experimental-select-control__input',
|
||||
id: `${ props.id }-input`,
|
||||
'aria-autocomplete': 'list',
|
||||
'aria-controls': `${ props.id }-menu`,
|
||||
'aria-activedescendant':
|
||||
highlightedIndex >= 0
|
||||
? `woocommerce-experimental-tree-control__menu-item-${ highlightedIndex }`
|
||||
: undefined,
|
||||
'aria-controls': menuInstanceId,
|
||||
'aria-owns': menuInstanceId,
|
||||
role: 'combobox',
|
||||
autoComplete: 'off',
|
||||
'aria-expanded': isOpen,
|
||||
'aria-haspopup': 'tree',
|
||||
disabled,
|
||||
onFocus: ( event ) => {
|
||||
if ( props.multiple ) {
|
||||
|
@ -141,44 +198,132 @@ export const SelectTree = function SelectTree( {
|
|||
}
|
||||
},
|
||||
onBlur: ( event ) => {
|
||||
if ( isOpen && isEventOutside( event ) ) {
|
||||
event.preventDefault();
|
||||
if ( isEventOutside( event ) ) {
|
||||
setIsOpen( false );
|
||||
setIsFocused( false );
|
||||
recalculateInputValue();
|
||||
}
|
||||
setIsFocused( false );
|
||||
},
|
||||
onKeyDown: ( event ) => {
|
||||
setIsOpen( true );
|
||||
if ( event.key === 'ArrowDown' ) {
|
||||
event.preventDefault();
|
||||
// focus on the first element from the Popover
|
||||
(
|
||||
document.querySelector(
|
||||
`#${ menuInstanceId } input, #${ menuInstanceId } button`
|
||||
) as HTMLInputElement | HTMLButtonElement
|
||||
)?.focus();
|
||||
}
|
||||
if ( event.key === 'Tab' || event.key === 'Escape' ) {
|
||||
if (
|
||||
// is advancing from the last menu item to the create button
|
||||
highlightedIndex === items.length - 1 &&
|
||||
shouldShowCreateButton?.( props.createValue )
|
||||
) {
|
||||
setHighlightedIndex( items.length );
|
||||
} else {
|
||||
const visibleNodeIndex = getVisibleNodeIndex(
|
||||
linkedTree,
|
||||
Math.min( highlightedIndex + 1, items.length ),
|
||||
'down'
|
||||
);
|
||||
if ( visibleNodeIndex !== undefined ) {
|
||||
setHighlightedIndex( visibleNodeIndex );
|
||||
}
|
||||
}
|
||||
} else if ( event.key === 'ArrowUp' ) {
|
||||
event.preventDefault();
|
||||
if ( highlightedIndex > 0 ) {
|
||||
const visibleNodeIndex = getVisibleNodeIndex(
|
||||
linkedTree,
|
||||
Math.max( highlightedIndex - 1, -1 ),
|
||||
'up'
|
||||
);
|
||||
if ( visibleNodeIndex !== undefined ) {
|
||||
setHighlightedIndex( visibleNodeIndex );
|
||||
}
|
||||
} else {
|
||||
setHighlightedIndex( -1 );
|
||||
}
|
||||
} else if ( event.key === 'Tab' || event.key === 'Escape' ) {
|
||||
setIsOpen( false );
|
||||
recalculateInputValue();
|
||||
}
|
||||
if ( event.key === ',' || event.key === 'Enter' ) {
|
||||
} else if ( event.key === 'Enter' || event.key === ',' ) {
|
||||
event.preventDefault();
|
||||
const item = items.find(
|
||||
( i ) => i.label === escapeHTML( inputValue )
|
||||
);
|
||||
const isAlreadySelected =
|
||||
Array.isArray( props.selected ) &&
|
||||
Boolean(
|
||||
props.selected.find(
|
||||
( i ) => i.label === escapeHTML( inputValue )
|
||||
)
|
||||
if (
|
||||
highlightedIndex === items.length &&
|
||||
shouldShowCreateButton
|
||||
) {
|
||||
props.onCreateNew?.();
|
||||
} else if (
|
||||
// is selecting an item
|
||||
highlightedIndex !== -1
|
||||
) {
|
||||
const nodeData = getNodeDataByIndex(
|
||||
linkedTree,
|
||||
highlightedIndex
|
||||
);
|
||||
if ( props.onSelect && item && ! isAlreadySelected ) {
|
||||
props.onSelect( item );
|
||||
setInputValue( '' );
|
||||
recalculateInputValue();
|
||||
if ( ! nodeData ) {
|
||||
return;
|
||||
}
|
||||
if ( props.multiple && Array.isArray( props.selected ) ) {
|
||||
if (
|
||||
! Boolean(
|
||||
props.selected.find(
|
||||
( i ) => i.label === nodeData.label
|
||||
)
|
||||
)
|
||||
) {
|
||||
if ( props.onSelect ) {
|
||||
props.onSelect( nodeData );
|
||||
}
|
||||
} else if ( props.onRemove ) {
|
||||
props.onRemove( nodeData );
|
||||
}
|
||||
setInputValue( '' );
|
||||
} else {
|
||||
onInputChange?.( nodeData.label );
|
||||
props.onSelect?.( nodeData );
|
||||
setIsOpen( false );
|
||||
setIsFocused( false );
|
||||
focusOnInput();
|
||||
}
|
||||
} else if ( inputValue ) {
|
||||
// no highlighted item, but there is an input value, check if it matches any item
|
||||
|
||||
const item = items.find(
|
||||
( i ) => i.label === escapeHTML( inputValue )
|
||||
);
|
||||
const isAlreadySelected = Array.isArray( props.selected )
|
||||
? Boolean(
|
||||
props.selected.find(
|
||||
( i ) =>
|
||||
i.label === escapeHTML( inputValue )
|
||||
)
|
||||
)
|
||||
: props.selected?.label === escapeHTML( inputValue );
|
||||
if ( item && ! isAlreadySelected ) {
|
||||
props.onSelect?.( item );
|
||||
setInputValue( '' );
|
||||
recalculateInputValue();
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
event.key === 'Backspace' &&
|
||||
// test if the cursor is at the beginning of the input with nothing selected
|
||||
( event.target as HTMLInputElement ).selectionStart === 0 &&
|
||||
( event.target as HTMLInputElement ).selectionEnd === 0 &&
|
||||
selectedItemsFocusHandle.current
|
||||
) {
|
||||
selectedItemsFocusHandle.current();
|
||||
} else if ( event.key === 'ArrowRight' ) {
|
||||
setLinkedTree(
|
||||
toggleNode( linkedTree, highlightedIndex, true )
|
||||
);
|
||||
} else if ( event.key === 'ArrowLeft' ) {
|
||||
setLinkedTree(
|
||||
toggleNode( linkedTree, highlightedIndex, false )
|
||||
);
|
||||
} else if ( event.key === 'Home' ) {
|
||||
event.preventDefault();
|
||||
setHighlightedIndex( 0 );
|
||||
} else if ( event.key === 'End' ) {
|
||||
event.preventDefault();
|
||||
setHighlightedIndex( items.length - 1 );
|
||||
}
|
||||
},
|
||||
onChange: ( event ) => {
|
||||
|
@ -219,7 +364,14 @@ export const SelectTree = function SelectTree( {
|
|||
<BaseControl
|
||||
label={ props.label }
|
||||
id={ `${ props.id }-input` }
|
||||
help={ help }
|
||||
help={
|
||||
props.multiple && ! help
|
||||
? __(
|
||||
'Separate with commas or the Enter key.',
|
||||
'woocommerce'
|
||||
)
|
||||
: help
|
||||
}
|
||||
>
|
||||
<>
|
||||
{ props.multiple ? (
|
||||
|
@ -227,16 +379,18 @@ export const SelectTree = function SelectTree( {
|
|||
comboBoxProps={ {
|
||||
className:
|
||||
'woocommerce-experimental-select-control__combo-box-wrapper',
|
||||
role: 'combobox',
|
||||
'aria-expanded': isOpen,
|
||||
'aria-haspopup': 'tree',
|
||||
'aria-owns': `${ props.id }-menu`,
|
||||
} }
|
||||
inputProps={ inputProps }
|
||||
suffix={
|
||||
<div className="woocommerce-experimental-select-control__suffix-items">
|
||||
{ isClearingAllowed && isOpen && (
|
||||
<Button onClick={ handleClear }>
|
||||
<Button
|
||||
label={ __(
|
||||
'Remove all',
|
||||
'woocommerce'
|
||||
) }
|
||||
onClick={ handleClear }
|
||||
>
|
||||
<SuffixIcon
|
||||
className="woocommerce-experimental-select-control__icon-clear"
|
||||
icon={ closeSmall }
|
||||
|
@ -253,7 +407,12 @@ export const SelectTree = function SelectTree( {
|
|||
>
|
||||
<SelectedItems
|
||||
isReadOnly={ isReadOnly }
|
||||
items={ ( props.selected as Item[] ) || [] }
|
||||
ref={ selectedItemsFocusHandle }
|
||||
items={
|
||||
! Array.isArray( props.selected )
|
||||
? [ props.selected ]
|
||||
: props.selected
|
||||
}
|
||||
getItemLabel={ ( item ) =>
|
||||
item?.label || ''
|
||||
}
|
||||
|
@ -262,12 +421,20 @@ export const SelectTree = function SelectTree( {
|
|||
}
|
||||
onRemove={ ( item ) => {
|
||||
if (
|
||||
item &&
|
||||
! Array.isArray( item ) &&
|
||||
props.onRemove
|
||||
) {
|
||||
props.onRemove( item );
|
||||
}
|
||||
} }
|
||||
onBlur={ ( event ) => {
|
||||
if ( isEventOutside( event ) ) {
|
||||
setIsOpen( false );
|
||||
setIsFocused( false );
|
||||
}
|
||||
} }
|
||||
onSelectedItemsEnd={ focusOnInput }
|
||||
getSelectedItemProps={ () => ( {} ) }
|
||||
/>
|
||||
</ComboBox>
|
||||
|
@ -311,8 +478,18 @@ export const SelectTree = function SelectTree( {
|
|||
isEventOutside={ isEventOutside }
|
||||
isLoading={ isLoading }
|
||||
isOpen={ isOpen }
|
||||
highlightedIndex={ highlightedIndex }
|
||||
onExpand={ ( index, value ) => {
|
||||
setLinkedTree(
|
||||
toggleNode( linkedTree, index, value )
|
||||
);
|
||||
} }
|
||||
items={ linkedTree }
|
||||
shouldShowCreateButton={ shouldShowCreateButton }
|
||||
onEscape={ () => {
|
||||
focusOnInput();
|
||||
setIsOpen( false );
|
||||
} }
|
||||
onClose={ () => {
|
||||
setIsOpen( false );
|
||||
} }
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { render } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { useState } from 'react';
|
||||
import React, { createElement } from '@wordpress/element';
|
||||
import { SelectTree } from '../select-tree';
|
||||
import { Item } from '../../experimental-tree-control';
|
||||
|
@ -26,6 +28,44 @@ const DEFAULT_PROPS = {
|
|||
placeholder: 'Type here',
|
||||
};
|
||||
|
||||
const TestComponent = ( { multiple }: { multiple?: boolean } ) => {
|
||||
const [ typedValue, setTypedValue ] = useState( '' );
|
||||
const [ selected, setSelected ] = useState< any >( [] );
|
||||
|
||||
return createElement( SelectTree, {
|
||||
...DEFAULT_PROPS,
|
||||
multiple,
|
||||
shouldShowCreateButton: () => true,
|
||||
onInputChange: ( value ) => {
|
||||
setTypedValue( value || '' );
|
||||
},
|
||||
createValue: typedValue,
|
||||
selected: Array.isArray( selected )
|
||||
? selected.map( ( i ) => ( {
|
||||
value: String( i.id ),
|
||||
label: i.name,
|
||||
} ) )
|
||||
: {
|
||||
value: String( selected.id ),
|
||||
label: selected.name,
|
||||
},
|
||||
onSelect: ( item: Item | Item[] ) =>
|
||||
item && Array.isArray( item )
|
||||
? setSelected(
|
||||
item.map( ( i ) => ( {
|
||||
id: +i.value,
|
||||
name: i.label,
|
||||
parent: i.parent ? +i.parent : 0,
|
||||
} ) )
|
||||
)
|
||||
: setSelected( {
|
||||
id: +item.value,
|
||||
name: item.label,
|
||||
parent: item.parent ? +item.parent : 0,
|
||||
} ),
|
||||
} );
|
||||
};
|
||||
|
||||
describe( 'SelectTree', () => {
|
||||
beforeEach( () => {
|
||||
jest.clearAllMocks();
|
||||
|
@ -36,7 +76,7 @@ describe( 'SelectTree', () => {
|
|||
<SelectTree { ...DEFAULT_PROPS } />
|
||||
);
|
||||
expect( queryByText( 'Item 1' ) ).not.toBeInTheDocument();
|
||||
queryByRole( 'textbox' )?.focus();
|
||||
queryByRole( 'combobox' )?.focus();
|
||||
expect( queryByText( 'Item 1' ) ).toBeInTheDocument();
|
||||
} );
|
||||
|
||||
|
@ -47,20 +87,21 @@ describe( 'SelectTree', () => {
|
|||
shouldShowCreateButton={ () => true }
|
||||
/>
|
||||
);
|
||||
queryByRole( 'textbox' )?.focus();
|
||||
queryByRole( 'combobox' )?.focus();
|
||||
expect( queryByText( 'Create new' ) ).toBeInTheDocument();
|
||||
} );
|
||||
it( 'should not show create button when callback is false or no callback', () => {
|
||||
const { queryByText, queryByRole } = render(
|
||||
<SelectTree { ...DEFAULT_PROPS } />
|
||||
);
|
||||
queryByRole( 'textbox' )?.focus();
|
||||
queryByRole( 'combobox' )?.focus();
|
||||
expect( queryByText( 'Create new' ) ).not.toBeInTheDocument();
|
||||
} );
|
||||
it( 'should show a root item when focused and child when expand button is clicked', () => {
|
||||
const { queryByText, queryByLabelText, queryByRole } =
|
||||
render( <SelectTree { ...DEFAULT_PROPS } /> );
|
||||
queryByRole( 'textbox' )?.focus();
|
||||
const { queryByText, queryByLabelText, queryByRole } = render(
|
||||
<SelectTree { ...DEFAULT_PROPS } />
|
||||
);
|
||||
queryByRole( 'combobox' )?.focus();
|
||||
expect( queryByText( 'Item 1' ) ).toBeInTheDocument();
|
||||
|
||||
expect( queryByText( 'Item 2' ) ).not.toBeInTheDocument();
|
||||
|
@ -72,7 +113,7 @@ describe( 'SelectTree', () => {
|
|||
const { queryAllByRole, queryByRole } = render(
|
||||
<SelectTree { ...DEFAULT_PROPS } selected={ [ mockItems[ 0 ] ] } />
|
||||
);
|
||||
queryByRole( 'textbox' )?.focus();
|
||||
queryByRole( 'combobox' )?.focus();
|
||||
expect( queryAllByRole( 'treeitem' )[ 0 ] ).toHaveAttribute(
|
||||
'aria-selected',
|
||||
'true'
|
||||
|
@ -87,7 +128,7 @@ describe( 'SelectTree', () => {
|
|||
shouldShowCreateButton={ () => true }
|
||||
/>
|
||||
);
|
||||
queryByRole( 'textbox' )?.focus();
|
||||
queryByRole( 'combobox' )?.focus();
|
||||
expect( queryByText( 'Create "new item"' ) ).toBeInTheDocument();
|
||||
} );
|
||||
it( 'should call onCreateNew when Create "<createValue>" button is clicked', () => {
|
||||
|
@ -100,8 +141,34 @@ describe( 'SelectTree', () => {
|
|||
onCreateNew={ mockFn }
|
||||
/>
|
||||
);
|
||||
queryByRole( 'textbox' )?.focus();
|
||||
queryByRole( 'combobox' )?.focus();
|
||||
queryByText( 'Create "new item"' )?.click();
|
||||
expect( mockFn ).toBeCalledTimes( 1 );
|
||||
} );
|
||||
it( 'correctly selects existing item in single mode with arrow keys', async () => {
|
||||
const { findByRole } = render( <TestComponent /> );
|
||||
const combobox = ( await findByRole( 'combobox' ) ) as HTMLInputElement;
|
||||
combobox.focus();
|
||||
userEvent.keyboard( '{arrowdown}{enter}' );
|
||||
expect( combobox.value ).toBe( 'Item 1' );
|
||||
} );
|
||||
it( 'correctly selects existing item in single mode by typing and pressing Enter', async () => {
|
||||
const { findByRole } = render( <TestComponent /> );
|
||||
const combobox = ( await findByRole( 'combobox' ) ) as HTMLInputElement;
|
||||
combobox.focus();
|
||||
userEvent.keyboard( 'Item 1{enter}' );
|
||||
userEvent.tab();
|
||||
expect( combobox.value ).toBe( 'Item 1' );
|
||||
} );
|
||||
it( 'correctly selects existing item in multiple mode by typing and pressing Enter', async () => {
|
||||
const { findByRole, getAllByText } = render(
|
||||
<TestComponent multiple />
|
||||
);
|
||||
const combobox = ( await findByRole( 'combobox' ) ) as HTMLInputElement;
|
||||
combobox.focus();
|
||||
userEvent.keyboard( 'Item 1' );
|
||||
userEvent.keyboard( '{enter}' );
|
||||
expect( combobox.value ).toBe( '' ); // input is cleared
|
||||
expect( getAllByText( 'Item 1' )[ 0 ] ).toBeInTheDocument(); // item is selected (turns into a token)
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useMemo } from 'react';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Item, LinkedTree } from '../types';
|
||||
|
||||
type MemoItems = {
|
||||
[ value: Item[ 'value' ] ]: LinkedTree;
|
||||
};
|
||||
|
||||
function findChildren(
|
||||
items: Item[],
|
||||
parent?: Item[ 'parent' ],
|
||||
memo: MemoItems = {}
|
||||
): LinkedTree[] {
|
||||
const children: Item[] = [];
|
||||
const others: Item[] = [];
|
||||
|
||||
items.forEach( ( item ) => {
|
||||
if ( item.parent === parent ) {
|
||||
children.push( item );
|
||||
} else {
|
||||
others.push( item );
|
||||
}
|
||||
memo[ item.value ] = {
|
||||
parent: undefined,
|
||||
data: item,
|
||||
children: [],
|
||||
};
|
||||
} );
|
||||
|
||||
return children.map( ( child ) => {
|
||||
const linkedTree = memo[ child.value ];
|
||||
linkedTree.parent = child.parent ? memo[ child.parent ] : undefined;
|
||||
linkedTree.children = findChildren( others, child.value, memo );
|
||||
return linkedTree;
|
||||
} );
|
||||
}
|
||||
|
||||
export function useLinkedTree( items: Item[] ): LinkedTree[] {
|
||||
const linkedTree = useMemo( () => {
|
||||
return findChildren( items, undefined, {} );
|
||||
}, [ items ] );
|
||||
|
||||
return linkedTree;
|
||||
}
|
|
@ -31,6 +31,10 @@ export function useTreeItem( {
|
|||
onLastItemLoop,
|
||||
onFirstItemLoop,
|
||||
onTreeBlur,
|
||||
onEscape,
|
||||
highlightedIndex,
|
||||
isHighlighted,
|
||||
onExpand,
|
||||
...props
|
||||
}: TreeItemProps ) {
|
||||
const nextLevel = level + 1;
|
||||
|
@ -78,16 +82,19 @@ export function useTreeItem( {
|
|||
getLabel,
|
||||
treeItemProps: {
|
||||
...props,
|
||||
role: 'none',
|
||||
id:
|
||||
'woocommerce-experimental-tree-control__menu-item-' +
|
||||
item.index,
|
||||
role: 'option',
|
||||
},
|
||||
headingProps: {
|
||||
role: 'treeitem',
|
||||
'aria-selected': selection.checkedStatus !== 'unchecked',
|
||||
'aria-expanded': item.children.length
|
||||
? expander.isExpanded
|
||||
? item.data.isExpanded
|
||||
: undefined,
|
||||
'aria-owns':
|
||||
item.children.length && expander.isExpanded
|
||||
item.children.length && item.data.isExpanded
|
||||
? subTreeId
|
||||
: undefined,
|
||||
style: {
|
||||
|
|
|
@ -10,7 +10,7 @@ import { TreeProps } from '../types';
|
|||
export function useTree( {
|
||||
items,
|
||||
level = 1,
|
||||
role = 'tree',
|
||||
role = 'listbox',
|
||||
multiple,
|
||||
selected,
|
||||
getItemLabel,
|
||||
|
@ -24,6 +24,9 @@ export function useTree( {
|
|||
onCreateNew,
|
||||
shouldShowCreateButton,
|
||||
onFirstItemLoop,
|
||||
onEscape,
|
||||
highlightedIndex,
|
||||
onExpand,
|
||||
...props
|
||||
}: TreeProps ) {
|
||||
return {
|
||||
|
|
|
@ -0,0 +1,211 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { AugmentedItem, Item, LinkedTree } from './types';
|
||||
|
||||
type MemoItems = {
|
||||
[ value: AugmentedItem[ 'value' ] ]: LinkedTree;
|
||||
};
|
||||
|
||||
const shouldItemBeExpanded = (
|
||||
item: LinkedTree,
|
||||
createValue: string | undefined
|
||||
): boolean => {
|
||||
if ( ! createValue || ! item.children?.length ) return false;
|
||||
return item.children.some( ( child ) => {
|
||||
if ( new RegExp( createValue || '', 'ig' ).test( child.data.label ) ) {
|
||||
return true;
|
||||
}
|
||||
return shouldItemBeExpanded( child, createValue );
|
||||
} );
|
||||
};
|
||||
|
||||
function findChildren(
|
||||
items: AugmentedItem[],
|
||||
memo: MemoItems = {},
|
||||
parent?: AugmentedItem[ 'parent' ],
|
||||
createValue?: string | undefined
|
||||
): LinkedTree[] {
|
||||
const children: AugmentedItem[] = [];
|
||||
const others: AugmentedItem[] = [];
|
||||
|
||||
items.forEach( ( item ) => {
|
||||
if ( item.parent === parent ) {
|
||||
children.push( item );
|
||||
} else {
|
||||
others.push( item );
|
||||
}
|
||||
memo[ item.value ] = {
|
||||
parent: undefined,
|
||||
data: item,
|
||||
children: [],
|
||||
};
|
||||
} );
|
||||
|
||||
return children.map( ( child ) => {
|
||||
const linkedTree = memo[ child.value ];
|
||||
linkedTree.parent = child.parent ? memo[ child.parent ] : undefined;
|
||||
linkedTree.children = findChildren(
|
||||
others,
|
||||
memo,
|
||||
child.value,
|
||||
createValue
|
||||
);
|
||||
linkedTree.data.isExpanded =
|
||||
linkedTree.children.length === 0
|
||||
? true
|
||||
: shouldItemBeExpanded( linkedTree, createValue );
|
||||
return linkedTree;
|
||||
} );
|
||||
}
|
||||
|
||||
function populateIndexes(
|
||||
linkedTree: LinkedTree[],
|
||||
startCount = 0
|
||||
): LinkedTree[] {
|
||||
let count = startCount;
|
||||
|
||||
function populate( tree: LinkedTree[] ): number {
|
||||
for ( const node of tree ) {
|
||||
node.index = count;
|
||||
count++;
|
||||
if ( node.children ) {
|
||||
count = populate( node.children );
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
populate( linkedTree );
|
||||
return linkedTree;
|
||||
}
|
||||
|
||||
// creates a linked tree from an array of Items
|
||||
export function createLinkedTree(
|
||||
items: Item[],
|
||||
value: string | undefined
|
||||
): LinkedTree[] {
|
||||
const augmentedItems = items.map( ( i ) => ( {
|
||||
...i,
|
||||
isExpanded: false,
|
||||
} ) );
|
||||
return populateIndexes(
|
||||
findChildren( augmentedItems, {}, undefined, value )
|
||||
);
|
||||
}
|
||||
|
||||
// Toggles the expanded state of a node in a linked tree
|
||||
export function toggleNode(
|
||||
tree: LinkedTree[],
|
||||
number: number,
|
||||
value: boolean
|
||||
): LinkedTree[] {
|
||||
return tree.map( ( node ) => {
|
||||
return {
|
||||
...node,
|
||||
children: node.children
|
||||
? toggleNode( node.children, number, value )
|
||||
: node.children,
|
||||
data: {
|
||||
...node.data,
|
||||
isExpanded:
|
||||
node.index === number ? value : node.data.isExpanded,
|
||||
},
|
||||
...( node.parent
|
||||
? {
|
||||
parent: {
|
||||
...node.parent,
|
||||
data: {
|
||||
...node.parent.data,
|
||||
isExpanded:
|
||||
node.parent.index === number
|
||||
? value
|
||||
: node.parent.data.isExpanded,
|
||||
},
|
||||
},
|
||||
}
|
||||
: {} ),
|
||||
};
|
||||
} );
|
||||
}
|
||||
|
||||
// Gets the index of the next/previous visible node in the linked tree
|
||||
export function getVisibleNodeIndex(
|
||||
tree: LinkedTree[],
|
||||
highlightedIndex: number,
|
||||
direction: 'up' | 'down'
|
||||
): number | undefined {
|
||||
if ( direction === 'down' ) {
|
||||
for ( const node of tree ) {
|
||||
if ( ! node.parent || node.parent.data.isExpanded ) {
|
||||
if (
|
||||
node.index !== undefined &&
|
||||
node.index >= highlightedIndex
|
||||
) {
|
||||
return node.index;
|
||||
}
|
||||
const visibleNodeIndex = getVisibleNodeIndex(
|
||||
node.children,
|
||||
highlightedIndex,
|
||||
direction
|
||||
);
|
||||
if ( visibleNodeIndex !== undefined ) {
|
||||
return visibleNodeIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for ( let i = tree.length - 1; i >= 0; i-- ) {
|
||||
const node = tree[ i ];
|
||||
if ( ! node.parent || node.parent.data.isExpanded ) {
|
||||
const visibleNodeIndex = getVisibleNodeIndex(
|
||||
node.children,
|
||||
highlightedIndex,
|
||||
direction
|
||||
);
|
||||
if ( visibleNodeIndex !== undefined ) {
|
||||
return visibleNodeIndex;
|
||||
}
|
||||
if (
|
||||
node.index !== undefined &&
|
||||
node.index <= highlightedIndex
|
||||
) {
|
||||
return node.index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Counts the number of nodes in a LinkedTree
|
||||
export function countNumberOfNodes( linkedTree: LinkedTree[] ) {
|
||||
let count = 0;
|
||||
for ( const node of linkedTree ) {
|
||||
count++;
|
||||
if ( node.children ) {
|
||||
count += countNumberOfNodes( node.children );
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// Gets the data of a node by its index
|
||||
export function getNodeDataByIndex(
|
||||
linkedTree: LinkedTree[],
|
||||
index: number
|
||||
): Item | undefined {
|
||||
for ( const node of linkedTree ) {
|
||||
if ( node.index === index ) {
|
||||
return node.data;
|
||||
}
|
||||
if ( node.children ) {
|
||||
const child = getNodeDataByIndex( node.children, index );
|
||||
if ( child ) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
|
@ -6,7 +6,7 @@ import { createElement, forwardRef } from 'react';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { useLinkedTree } from './hooks/use-linked-tree';
|
||||
import { createLinkedTree } from './linked-tree-utils';
|
||||
import { Tree } from './tree';
|
||||
import { TreeControlProps } from './types';
|
||||
|
||||
|
@ -14,7 +14,7 @@ export const TreeControl = forwardRef( function ForwardedTree(
|
|||
{ items, ...props }: TreeControlProps,
|
||||
ref: React.ForwardedRef< HTMLOListElement >
|
||||
) {
|
||||
const linkedTree = useLinkedTree( items );
|
||||
const linkedTree = createLinkedTree( items, props.createValue );
|
||||
|
||||
return <Tree { ...props } ref={ ref } items={ linkedTree } />;
|
||||
} );
|
||||
|
|
|
@ -6,6 +6,8 @@ $control-size: $gap-large;
|
|||
&--highlighted {
|
||||
> .experimental-woocommerce-tree-item__heading {
|
||||
background-color: $gray-100;
|
||||
outline: 1.5px solid var( --wp-admin-theme-color );
|
||||
outline-offset: -1.5px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,21 +24,25 @@ export const TreeItem = forwardRef( function ForwardedTreeItem(
|
|||
treeItemProps,
|
||||
headingProps,
|
||||
treeProps,
|
||||
expander: { isExpanded, onToggleExpand },
|
||||
selection,
|
||||
highlighter: { isHighlighted },
|
||||
getLabel,
|
||||
} = useTreeItem( {
|
||||
...props,
|
||||
ref,
|
||||
} );
|
||||
|
||||
function handleEscapePress(
|
||||
event: React.KeyboardEvent< HTMLInputElement >
|
||||
) {
|
||||
function handleKeyDown( event: React.KeyboardEvent< HTMLElement > ) {
|
||||
if ( event.key === 'Escape' && props.onEscape ) {
|
||||
event.preventDefault();
|
||||
props.onEscape();
|
||||
} else if ( event.key === 'ArrowLeft' ) {
|
||||
if ( item.index !== undefined ) {
|
||||
props.onExpand?.( item.index, false );
|
||||
}
|
||||
} else if ( event.key === 'ArrowRight' ) {
|
||||
if ( item.index !== undefined ) {
|
||||
props.onExpand?.( item.index, true );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,7 +54,7 @@ export const TreeItem = forwardRef( function ForwardedTreeItem(
|
|||
'experimental-woocommerce-tree-item',
|
||||
{
|
||||
'experimental-woocommerce-tree-item--highlighted':
|
||||
isHighlighted,
|
||||
props.isHighlighted,
|
||||
}
|
||||
) }
|
||||
>
|
||||
|
@ -67,7 +71,7 @@ export const TreeItem = forwardRef( function ForwardedTreeItem(
|
|||
}
|
||||
checked={ selection.checkedStatus === 'checked' }
|
||||
onChange={ selection.onSelectChild }
|
||||
onKeyDown={ handleEscapePress }
|
||||
onKeyDown={ handleKeyDown }
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore __nextHasNoMarginBottom is a valid prop
|
||||
__nextHasNoMarginBottom={ true }
|
||||
|
@ -80,7 +84,7 @@ export const TreeItem = forwardRef( function ForwardedTreeItem(
|
|||
onChange={ ( event ) =>
|
||||
selection.onSelectChild( event.target.checked )
|
||||
}
|
||||
onKeyDown={ handleEscapePress }
|
||||
onKeyDown={ handleKeyDown }
|
||||
/>
|
||||
) }
|
||||
|
||||
|
@ -94,11 +98,21 @@ export const TreeItem = forwardRef( function ForwardedTreeItem(
|
|||
{ Boolean( item.children?.length ) && (
|
||||
<div className="experimental-woocommerce-tree-item__expander">
|
||||
<Button
|
||||
icon={ isExpanded ? chevronUp : chevronDown }
|
||||
onClick={ onToggleExpand }
|
||||
icon={
|
||||
item.data.isExpanded ? chevronUp : chevronDown
|
||||
}
|
||||
onClick={ () => {
|
||||
if ( item.index !== undefined ) {
|
||||
props.onExpand?.(
|
||||
item.index,
|
||||
! item.data.isExpanded
|
||||
);
|
||||
}
|
||||
} }
|
||||
onKeyDown={ handleKeyDown }
|
||||
className="experimental-woocommerce-tree-item__expander"
|
||||
aria-label={
|
||||
isExpanded
|
||||
item.data.isExpanded
|
||||
? __( 'Collapse', 'woocommerce' )
|
||||
: __( 'Expand', 'woocommerce' )
|
||||
}
|
||||
|
@ -107,8 +121,13 @@ export const TreeItem = forwardRef( function ForwardedTreeItem(
|
|||
) }
|
||||
</div>
|
||||
|
||||
{ Boolean( item.children.length ) && isExpanded && (
|
||||
<Tree { ...treeProps } />
|
||||
{ Boolean( item.children.length ) && item.data.isExpanded && (
|
||||
<Tree
|
||||
{ ...treeProps }
|
||||
highlightedIndex={ props.highlightedIndex }
|
||||
onExpand={ props.onExpand }
|
||||
onEscape={ props.onEscape }
|
||||
/>
|
||||
) }
|
||||
</li>
|
||||
);
|
||||
|
|
|
@ -16,8 +16,9 @@
|
|||
width: 100%;
|
||||
cursor: default;
|
||||
&:hover,
|
||||
&:focus-within {
|
||||
outline: 1.5px solid var( --wp-admin-theme-color );
|
||||
&:focus-within,
|
||||
&--highlighted {
|
||||
outline: 1.5px solid var(--wp-admin-theme-color);
|
||||
outline-offset: -1.5px;
|
||||
background-color: $gray-100;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import { useMergeRefs } from '@wordpress/compose';
|
|||
import { useTree } from './hooks/use-tree';
|
||||
import { TreeItem } from './tree-item';
|
||||
import { TreeProps } from './types';
|
||||
import { countNumberOfNodes } from './linked-tree-utils';
|
||||
|
||||
export const Tree = forwardRef( function ForwardedTree(
|
||||
props: TreeProps,
|
||||
|
@ -27,6 +28,8 @@ export const Tree = forwardRef( function ForwardedTree(
|
|||
ref,
|
||||
} );
|
||||
|
||||
const numberOfItems = countNumberOfNodes( items );
|
||||
|
||||
const isCreateButtonVisible =
|
||||
props.shouldShowCreateButton &&
|
||||
props.shouldShowCreateButton( props.createValue );
|
||||
|
@ -45,7 +48,12 @@ export const Tree = forwardRef( function ForwardedTree(
|
|||
{ items.map( ( child, index ) => (
|
||||
<TreeItem
|
||||
{ ...treeItemProps }
|
||||
isExpanded={ props.isExpanded }
|
||||
isHighlighted={
|
||||
props.highlightedIndex === child.index
|
||||
}
|
||||
onExpand={ props.onExpand }
|
||||
highlightedIndex={ props.highlightedIndex }
|
||||
isExpanded={ child.data.isExpanded }
|
||||
key={ child.data.value }
|
||||
item={ child }
|
||||
index={ index }
|
||||
|
@ -53,7 +61,7 @@ export const Tree = forwardRef( function ForwardedTree(
|
|||
onLastItemLoop={ () => {
|
||||
(
|
||||
rootListRef.current
|
||||
?.closest( 'ol[role="tree"]' )
|
||||
?.closest( 'ol[role="listbox"]' )
|
||||
?.parentElement?.querySelector(
|
||||
'.experimental-woocommerce-tree__button'
|
||||
) as HTMLButtonElement
|
||||
|
@ -67,7 +75,17 @@ export const Tree = forwardRef( function ForwardedTree(
|
|||
) : null }
|
||||
{ isCreateButtonVisible && (
|
||||
<Button
|
||||
className="experimental-woocommerce-tree__button"
|
||||
id={
|
||||
'woocommerce-experimental-tree-control__menu-item-' +
|
||||
numberOfItems
|
||||
}
|
||||
className={ classNames(
|
||||
'experimental-woocommerce-tree__button',
|
||||
{
|
||||
'experimental-woocommerce-tree__button--highlighted':
|
||||
props.highlightedIndex === numberOfItems,
|
||||
}
|
||||
) }
|
||||
onClick={ () => {
|
||||
if ( props.onCreateNew ) {
|
||||
props.onCreateNew();
|
||||
|
|
|
@ -4,10 +4,15 @@ export interface Item {
|
|||
label: string;
|
||||
}
|
||||
|
||||
export type AugmentedItem = Item & {
|
||||
isExpanded: boolean;
|
||||
};
|
||||
|
||||
export interface LinkedTree {
|
||||
parent?: LinkedTree;
|
||||
data: Item;
|
||||
data: AugmentedItem;
|
||||
children: LinkedTree[];
|
||||
index?: number;
|
||||
}
|
||||
|
||||
export type CheckedStatus = 'checked' | 'unchecked' | 'indeterminate';
|
||||
|
@ -18,6 +23,11 @@ type BaseTreeProps = {
|
|||
* a list of items if it is true.
|
||||
*/
|
||||
selected?: Item | Item[];
|
||||
|
||||
onExpand?( index: number, value: boolean ): void;
|
||||
|
||||
highlightedIndex?: number;
|
||||
|
||||
/**
|
||||
* Whether the tree items are single or multiple selected.
|
||||
*/
|
||||
|
@ -137,6 +147,7 @@ export type TreeItemProps = BaseTreeProps &
|
|||
item: LinkedTree;
|
||||
index: number;
|
||||
isFocused?: boolean;
|
||||
isHighlighted?: boolean;
|
||||
getLabel?( item: LinkedTree ): JSX.Element;
|
||||
shouldItemBeExpanded?( item: LinkedTree ): boolean;
|
||||
onLastItemLoop?( event: React.KeyboardEvent< HTMLDivElement > ): void;
|
||||
|
|
|
@ -87,7 +87,7 @@ describe( 'Search', () => {
|
|||
userEvent.type( getByRole( 'combobox' ), 'A' );
|
||||
// Wait for async options processing.
|
||||
await waitFor( () => {
|
||||
expect( optionsSpy ).toBeCalledWith( 'A' );
|
||||
expect( optionsSpy ).toHaveBeenCalledWith( 'A' );
|
||||
} );
|
||||
await waitFor( () => {
|
||||
expect( queryAllByRole( 'option' ) ).toHaveLength( 3 );
|
||||
|
@ -119,7 +119,7 @@ describe( 'Search', () => {
|
|||
userEvent.type( getByRole( 'combobox' ), 'A' );
|
||||
// Wait for async options processing.
|
||||
await waitFor( () => {
|
||||
expect( optionsSpy ).toBeCalledWith( 'A' );
|
||||
expect( optionsSpy ).toHaveBeenCalledWith( 'A' );
|
||||
} );
|
||||
await waitFor( () => {
|
||||
expect( queryAllByRole( 'option' ) ).toHaveLength( 3 );
|
||||
|
|
|
@ -2,16 +2,20 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { createElement, Fragment, useState } from '@wordpress/element';
|
||||
import {
|
||||
createElement,
|
||||
forwardRef,
|
||||
Fragment,
|
||||
useState,
|
||||
} from '@wordpress/element';
|
||||
import classnames from 'classnames';
|
||||
import { Button, Popover } from '@wordpress/components';
|
||||
import { Icon, closeSmall } from '@wordpress/icons';
|
||||
import { decodeEntities } from '@wordpress/html-entities';
|
||||
import { withInstanceId } from '@wordpress/compose';
|
||||
import { Ref } from 'react';
|
||||
import { useInstanceId } from '@wordpress/compose';
|
||||
|
||||
type Props = {
|
||||
/** A unique ID for this instance of the component. This is automatically generated by withInstanceId. */
|
||||
instanceId: number | string;
|
||||
/** The name for this item, displayed as the tag's text. */
|
||||
label: string;
|
||||
/** A unique ID for this item. This is used to identify the item when the remove button is clicked. */
|
||||
|
@ -28,72 +32,84 @@ type Props = {
|
|||
className?: string;
|
||||
};
|
||||
|
||||
const Tag: React.VFC< Props > = ( {
|
||||
id,
|
||||
instanceId,
|
||||
label,
|
||||
popoverContents,
|
||||
remove,
|
||||
screenReaderLabel,
|
||||
className,
|
||||
} ) => {
|
||||
const [ isVisible, setIsVisible ] = useState( false );
|
||||
const Tag = forwardRef(
|
||||
(
|
||||
{
|
||||
id,
|
||||
label,
|
||||
popoverContents,
|
||||
remove,
|
||||
screenReaderLabel,
|
||||
className,
|
||||
}: Props,
|
||||
removeButtonRef: Ref< HTMLButtonElement >
|
||||
) => {
|
||||
const [ isVisible, setIsVisible ] = useState( false );
|
||||
|
||||
screenReaderLabel = screenReaderLabel || label;
|
||||
if ( ! label ) {
|
||||
// A null label probably means something went wrong
|
||||
// @todo Maybe this should be a loading indicator?
|
||||
return null;
|
||||
}
|
||||
label = decodeEntities( label );
|
||||
const classes = classnames( 'woocommerce-tag', className, {
|
||||
'has-remove': !! remove,
|
||||
} );
|
||||
const labelId = `woocommerce-tag__label-${ instanceId }`;
|
||||
const labelTextNode = (
|
||||
<Fragment>
|
||||
<span className="screen-reader-text">{ screenReaderLabel }</span>
|
||||
<span aria-hidden="true">{ label }</span>
|
||||
</Fragment>
|
||||
);
|
||||
const instanceId = useInstanceId( Tag ) as string;
|
||||
|
||||
return (
|
||||
<span className={ classes }>
|
||||
{ popoverContents ? (
|
||||
<Button
|
||||
className="woocommerce-tag__text"
|
||||
id={ labelId }
|
||||
onClick={ () => setIsVisible( true ) }
|
||||
>
|
||||
{ labelTextNode }
|
||||
</Button>
|
||||
) : (
|
||||
<span className="woocommerce-tag__text" id={ labelId }>
|
||||
{ labelTextNode }
|
||||
screenReaderLabel = screenReaderLabel || label;
|
||||
if ( ! label ) {
|
||||
// A null label probably means something went wrong
|
||||
// @todo Maybe this should be a loading indicator?
|
||||
return null;
|
||||
}
|
||||
label = decodeEntities( label );
|
||||
const classes = classnames( 'woocommerce-tag', className, {
|
||||
'has-remove': !! remove,
|
||||
} );
|
||||
const labelId = `woocommerce-tag__label-${ instanceId }`;
|
||||
const labelTextNode = (
|
||||
<Fragment>
|
||||
<span className="screen-reader-text">
|
||||
{ screenReaderLabel }
|
||||
</span>
|
||||
) }
|
||||
{ popoverContents && isVisible && (
|
||||
<Popover onClose={ () => setIsVisible( false ) }>
|
||||
{ popoverContents }
|
||||
</Popover>
|
||||
) }
|
||||
{ remove && (
|
||||
<Button
|
||||
className="woocommerce-tag__remove"
|
||||
onClick={ remove( id ) }
|
||||
// translators: %s is the name of the tag being removed.
|
||||
label={ sprintf( __( 'Remove %s', 'woocommerce' ), label ) }
|
||||
aria-describedby={ labelId }
|
||||
>
|
||||
<Icon
|
||||
icon={ closeSmall }
|
||||
size={ 20 }
|
||||
className="clear-icon"
|
||||
/>
|
||||
</Button>
|
||||
) }
|
||||
</span>
|
||||
);
|
||||
};
|
||||
<span aria-hidden="true">{ label }</span>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
export default withInstanceId( Tag );
|
||||
return (
|
||||
<span className={ classes }>
|
||||
{ popoverContents ? (
|
||||
<Button
|
||||
className="woocommerce-tag__text"
|
||||
id={ labelId }
|
||||
onClick={ () => setIsVisible( true ) }
|
||||
>
|
||||
{ labelTextNode }
|
||||
</Button>
|
||||
) : (
|
||||
<span className="woocommerce-tag__text" id={ labelId }>
|
||||
{ labelTextNode }
|
||||
</span>
|
||||
) }
|
||||
{ popoverContents && isVisible && (
|
||||
<Popover onClose={ () => setIsVisible( false ) }>
|
||||
{ popoverContents }
|
||||
</Popover>
|
||||
) }
|
||||
{ remove && (
|
||||
<Button
|
||||
className="woocommerce-tag__remove"
|
||||
ref={ removeButtonRef }
|
||||
onClick={ remove( id ) }
|
||||
label={ sprintf(
|
||||
// translators: %s is the name of the tag being removed.
|
||||
__( 'Remove %s', 'woocommerce' ),
|
||||
label
|
||||
) }
|
||||
aria-describedby={ labelId }
|
||||
>
|
||||
<Icon
|
||||
icon={ closeSmall }
|
||||
size={ 20 }
|
||||
className="clear-icon"
|
||||
/>
|
||||
</Button>
|
||||
) }
|
||||
</span>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default Tag;
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: tweak
|
||||
|
||||
Rename Google Listings and Ads with Google for WooCommerce #### Comment <!-- If the changes in this pull request don't warrant a changelog entry, you can alternatively supply a comment here. Note that comments are only accepted with a significance of "Patch" -->
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: tweak
|
||||
|
||||
Fix typos in resolvers error message
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: tweak
|
||||
|
||||
Rename Google Listings and Ads with Google for WooCommerce
|
|
@ -52,7 +52,7 @@ export const pluginNames = {
|
|||
'Mercado Pago payments for WooCommerce',
|
||||
'woocommerce'
|
||||
),
|
||||
'google-listings-and-ads': __( 'Google Listings and Ads', 'woocommerce' ),
|
||||
'google-listings-and-ads': __( 'Google for WooCommerce', 'woocommerce' ),
|
||||
'woo-razorpay': __( 'Razorpay', 'woocommerce' ),
|
||||
mailpoet: __( 'MailPoet', 'woocommerce' ),
|
||||
'pinterest-for-woocommerce': __(
|
||||
|
|
|
@ -35,7 +35,7 @@ const getIntHeaderValues = (
|
|||
const value = response.headers.get( key );
|
||||
if ( value === undefined ) {
|
||||
throw new Error(
|
||||
`Malformed response from server. '${ key }' header is missing when retriving ./report/${ endpoint }.`
|
||||
`Malformed response from server. '${ key }' header is missing when retrieving ./report/${ endpoint }.`
|
||||
);
|
||||
}
|
||||
return parseInt( value, 10 );
|
||||
|
|
|
@ -26,7 +26,7 @@ export function* getReviews( query: ReviewsQueryParams ) {
|
|||
|
||||
if ( totalCountFromHeader === undefined ) {
|
||||
throw new Error(
|
||||
"Malformed response from server. 'x-wp-total' header is missing when retriving ./products/reviews."
|
||||
"Malformed response from server. 'x-wp-total' header is missing when retrieving ./products/reviews."
|
||||
);
|
||||
}
|
||||
const totalCount = parseInt( totalCountFromHeader, 10 );
|
||||
|
|
|
@ -2,11 +2,48 @@
|
|||
|
||||
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [3.2.0](https://www.npmjs.com/package/@woocommerce/packages/js/experimental/v/3.2.0) - 2022-07-08
|
||||
## [3.3.0](https://www.npmjs.com/package/@woocommerce/experimental/v/3.3.0) - 2024-07-25
|
||||
|
||||
- Patch - Added in missing TS definitions in package.json [#34154]
|
||||
- Patch - Check for note actions before checking length [#35396]
|
||||
- Patch - Corrected build configuration for packages that weren't outputting minified code. [#43716]
|
||||
- Patch - Fix invalid return callback ref warning [#37655]
|
||||
- Patch - Fix Launch Your Store task item should not be clickable once completed [#46361]
|
||||
- Patch - Fix missing fills prop in useSlotFills return object for wp.components >= 21.2.0 [#36887]
|
||||
- Patch - Fix remote inbox layout overflows the page width [#47451]
|
||||
- Minor - Bump node version. [#45148]
|
||||
- Patch - bump php version in packages/js/*/composer.json [#42020]
|
||||
- Patch - Support direction prop to control which direction hidden items open. [#36806]
|
||||
- Patch - update references to woocommerce.com to now reference woo.com [#41241]
|
||||
- Patch - Update TaskItem to include a badge next to the title. Update also related components TaskList and SetupTaskList, as well as docs, storybook, and tests. [#40034]
|
||||
- Patch - Update Woo.com references to WooCommerce.com. [#46259]
|
||||
- Patch - Add missing type definitions and add babel config for tests [#34428]
|
||||
- Minor - Adjust build/test scripts to remove -- -- that was required for pnpm 6. [#34661]
|
||||
- Minor - Fix lint issues [#36988]
|
||||
- Minor - Fix node and pnpm versions via engines [#34773]
|
||||
- Minor - Improve the "Dismiss" button visibility [#35060]
|
||||
- Patch - Make eslint emit JSON report for annotating PRs. [#39704]
|
||||
- Minor - Match TypeScript version with syncpack [#34787]
|
||||
- Patch - Merging trunk with local [#34322]
|
||||
- Minor - Sync @wordpress package versions via syncpack. [#37034]
|
||||
- Patch - Update dependencies [#48645]
|
||||
- Patch - Update eslint to 8.32.0 across the monorepo. [#36700]
|
||||
- Patch - Update events that should trigger the test job(s) [#47612]
|
||||
- Minor - Update pnpm monorepo-wide to 8.6.5 [#38990]
|
||||
- Minor - Update pnpm to 8.6.7 [#39245]
|
||||
- Patch - Update pnpm to 9.1.0 [#47385]
|
||||
- Minor - Update pnpm to version 8. [#37915]
|
||||
- Minor - Update pnpm version constraint to 7.13.3 to avoid auto-install-peers issues [#35007]
|
||||
- Patch - Update webpack config to use @woocommerce/internal-style-build's parser config [#37195]
|
||||
- Patch - Upgraded Storybook to 6.5.17-alpha.0 for TypeScript 5 compatibility [#39745]
|
||||
- Minor - Upgrade TypeScript to 5.1.6 [#39531]
|
||||
- Patch - Correct spelling errors [#37887]
|
||||
|
||||
## [3.2.0](https://www.npmjs.com/package/@woocommerce/experimental/v/3.2.0) - 2022-07-08
|
||||
|
||||
- Minor - Remove PHP and Composer dependencies for packaged JS packages
|
||||
|
||||
## [3.1.0](https://www.npmjs.com/package/@woocommerce/packages/js/experimental/v/3.1.0) - 2022-06-14
|
||||
## [3.1.0](https://www.npmjs.com/package/@woocommerce/experimental/v/3.1.0) - 2022-06-14
|
||||
|
||||
- Minor - Add Jetpack Changelogger
|
||||
- Minor - Update TaskItem props type definition.
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
Comment: Just changing package.json command for lint
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
Comment: Only a change to development tooling.
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
Comment: This is a CI-only change.
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
Comment: This is a developer-only build tooling related change.
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Corrected build configuration for packages that weren't outputting minified code.
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
Comment: adds `glob`, `rimraf`, and `uuid` to Syncpack
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Fix Launch Your Store task item should not be clickable once completed
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
Comment: Fix a persistent build bug where TS would try compile files outside of src and typings in packages/js/experimental
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: update
|
||||
|
||||
bump php version in packages/js/*/composer.json
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
Update events that should trigger the test job(s)
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: dev
|
||||
|
||||
Improve the "Dismiss" button visibility
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: dev
|
||||
|
||||
Update pnpm version constraint to 7.13.3 to avoid auto-install-peers issues
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
Update eslint to 8.32.0 across the monorepo.
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
Add missing type definitions and add babel config for tests
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: dev
|
||||
|
||||
Fix node and pnpm versions via engines
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
Make eslint emit JSON report for annotating PRs.
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: dev
|
||||
|
||||
Sync @wordpress package versions via syncpack.
|
|
@ -1,5 +0,0 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
Comment: Package scripts were modified to support simplified running of turbo commands in the monorepo.
|
||||
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: dev
|
||||
|
||||
Update pnpm to 8.6.7
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: dev
|
||||
|
||||
Fix lint issues
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: dev
|
||||
|
||||
Update pnpm monorepo-wide to 8.6.5
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: dev
|
||||
|
||||
Update pnpm to version 8.
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue