Merge branch 'trunk' into fix/tax_lookup_and_order_stat_deletion

This commit is contained in:
Nestor Soriano 2023-03-09 16:15:57 +01:00
commit a203681b96
No known key found for this signature in database
GPG Key ID: 08110F3518C12CAD
830 changed files with 14443 additions and 5988 deletions

View File

@ -2,20 +2,34 @@ codecov:
notify: notify:
require_ci_to_pass: yes require_ci_to_pass: yes
ignore:
- '**/tests'
- '**/test'
- 'tools/**'
- 'packages/js/admin-e2e-tests'
- 'packages/js/api-core-tests'
- 'packages/js/create-woo-extension'
- 'packages/js/e2e-core-tests'
- 'packages/js/e2e-environment'
- 'packages/js/e2e-utils'
- 'packages/js/eslint-plugin'
- 'packages/js/internal-e2e-builds'
- 'packages/js/internal-js-tests'
- 'packages/js/internal-style-build'
- '**/*.test.*'
coverage: coverage:
precision: 2 precision: 1
round: nearest round: nearest
range: "50...100" range: '50...80'
status: status:
project: project:
default: default:
informational: true target: auto
patch: patch:
default: default:
informational: true target: auto
changes: off
parsers: parsers:
gcov: gcov:
branch_detection: branch_detection:
@ -24,4 +38,9 @@ parsers:
method: no method: no
macro: no macro: no
comment: false comment:
layout: 'reach, diff, flags, files'
behavior: default
require_changes: false
require_base: no
require_head: yes

View File

@ -14,15 +14,13 @@
Closes # . Closes # .
<!-- The next section is mandatory. If your PR doesn't require testing, please indicate that you are purposefully omitting instructions. -->
- [ ] This PR is a very minor change/addition and does not require testing instructions (if checked you can ignore/remove the next section).
<!-- Begin testing instructions --> <!-- Begin testing instructions -->
### How to test the changes in this Pull Request: ### How to test the changes in this Pull Request:
<!-- Otherwise, please include detailed instructions on how these changes can be tested (including pre-conditions, configuration, steps to take and expected results). It may help to write your instructions using pseudocode -- as if you're telling a computer how to execute the test. --> <!-- Please include detailed instructions on how these changes can be tested, make sure to review and follow the guide for writing high-quality testing instructions below. -->
- [ ] Have you followed the [Writing high-quality testing instructions guide](https://github.com/woocommerce/woocommerce/wiki/Writing-high-quality-testing-instructions)?
1. 1.
2. 2.
@ -35,6 +33,7 @@ Closes # .
- [ ] Have you added an explanation of what your changes do and why you'd like us to include them? - [ ] Have you added an explanation of what your changes do and why you'd like us to include them?
- [ ] Have you written new tests for your changes, as applicable? - [ ] Have you written new tests for your changes, as applicable?
- [ ] Have you created a changelog file for each project being changed, ie `pnpm --filter=<project> changelog add`? - [ ] Have you created a changelog file for each project being changed, ie `pnpm --filter=<project> changelog add`?
- [ ] Have you included testing instructions?
<!-- Mark completed items with an [x] --> <!-- Mark completed items with an [x] -->

View File

@ -61,7 +61,7 @@
'plugin: woocommerce': 'plugin: woocommerce':
- plugins/woocommerce/**/* - plugins/woocommerce/**/*
'focus: react admin': 'focus: react admin [team:Ghidorah]':
- plugins/woocommerce/src/Admin/**/* - plugins/woocommerce/src/Admin/**/*
- plugins/woocommerce/src/Internal/Admin/**/* - plugins/woocommerce/src/Internal/Admin/**/*
- plugins/woocommerce-admin/**/* - plugins/woocommerce-admin/**/*

View File

@ -3,7 +3,7 @@ on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
packages: packages:
description: 'Enter a specific package to release, or packages separated by commas, ie @woocommerce/components,@woocommerce/number. Leaving this input to the default "-a" will prepare to release all eligible packages.' description: 'Enter a specific package to release, or packages separated by commas, ie @woocommerce/components,@woocommerce/number. Leaving this input to the default "-a" will prepare to release all eligible packages. When releasing a package for the first time, pass the "--initialRelease" flag.'
required: false required: false
default: '-a' default: '-a'
@ -14,7 +14,7 @@ jobs:
name: Run prepare script name: Run prepare script
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
permissions: permissions:
contents: read contents: write
pull-requests: write pull-requests: write
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3

View File

@ -2,6 +2,10 @@ name: 'Pull request post-merge processing'
on: on:
pull_request_target: pull_request_target:
types: [closed] types: [closed]
paths:
- 'packages/**'
- 'plugins/woocommerce/**'
- 'plugins/woocommerce-admin/**'
permissions: {} permissions: {}

View File

@ -10,8 +10,8 @@ if ( getenv( 'TIME_OVERRIDE' ) ) {
$base_dir = dirname( dirname( dirname( __DIR__ ) ) ); $base_dir = dirname( dirname( dirname( __DIR__ ) ) );
// The release date is 26 days after the code freeze. // The release date is 22 days after the code freeze.
$release_time = strtotime( '+26 days', $now ); $release_time = strtotime( '+22 days', $now );
$release_date = date( 'Y-m-d', $release_time ); $release_date = date( 'Y-m-d', $release_time );
$readme_file = $base_dir . '/plugins/woocommerce/readme.txt'; $readme_file = $base_dir . '/plugins/woocommerce/readme.txt';

View File

@ -0,0 +1,21 @@
#!/usr/bin/env bash
#
# Verify that the PHP version in the launched WP ENV environment is equal to expected.
#
cd $GITHUB_WORKSPACE/plugins/woocommerce
ACTUAL_PHP_VERSION=$(pnpm exec wp-env run tests-cli "wp --info | grep 'PHP version:'")
EXIT_CODE=''
echo "PHP version found in WP Env environment: \"$ACTUAL_PHP_VERSION\""
echo "Expected PHP version: \"$EXPECTED_PHP_VERSION\""
if [[ $ACTUAL_PHP_VERSION == *"$EXPECTED_PHP_VERSION"* ]]
then
EXIT_CODE=0
else
EXIT_CODE=1
fi
exit $EXIT_CODE

View File

@ -8,7 +8,6 @@ env:
API_ARTIFACT: api-daily--run-${{ github.run_number }} API_ARTIFACT: api-daily--run-${{ github.run_number }}
E2E_ARTIFACT: e2e-daily--run-${{ github.run_number }} E2E_ARTIFACT: e2e-daily--run-${{ github.run_number }}
FORCE_COLOR: 1 FORCE_COLOR: 1
BRANCH_NAME: ${{ github.ref_name }}
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
@ -25,10 +24,15 @@ jobs:
env: env:
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/test-results/allure-results ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/test-results/allure-results
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/test-results/allure-report ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/test-results/allure-report
BASE_URL: ${{ secrets.SMOKE_TEST_URL }}
ADMIN_USER: ${{ secrets.SMOKE_TEST_ADMIN_USER }}
ADMIN_PASSWORD: ${{ secrets.SMOKE_TEST_ADMIN_PASSWORD }}
ADMIN_USER_EMAIL: ${{ secrets.SMOKE_TEST_ADMIN_USER_EMAIL }}
CUSTOMER_USER: ${{ secrets.SMOKE_TEST_CUSTOMER_USER }}
CUSTOMER_PASSWORD: ${{ secrets.SMOKE_TEST_CUSTOMER_PASSWORD }}
DEFAULT_TIMEOUT_OVERRIDE: 120000
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with:
ref: ${{ env.BRANCH_NAME }}
- name: Setup WooCommerce Monorepo - name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo uses: ./.github/actions/setup-woocommerce-monorepo
@ -36,13 +40,21 @@ jobs:
install-filters: woocommerce install-filters: woocommerce
build: false build: false
- name: Download and install Chromium browser.
working-directory: plugins/woocommerce
run: pnpm exec playwright install chromium
- name: Run 'Update WooCommerce' test.
working-directory: plugins/woocommerce
env:
UPDATE_WC: nightly
run: pnpm exec playwright test --config=tests/e2e-pw/daily.playwright.config.js update-woocommerce.spec.js
- name: Run API tests. - name: Run API tests.
working-directory: plugins/woocommerce working-directory: plugins/woocommerce
env: env:
BASE_URL: ${{ secrets.SMOKE_TEST_URL }}
USER_KEY: ${{ secrets.SMOKE_TEST_ADMIN_USER }} USER_KEY: ${{ secrets.SMOKE_TEST_ADMIN_USER }}
USER_SECRET: ${{ secrets.SMOKE_TEST_ADMIN_PASSWORD }} USER_SECRET: ${{ secrets.SMOKE_TEST_ADMIN_PASSWORD }}
DEFAULT_TIMEOUT_OVERRIDE: 120000
run: pnpm exec playwright test --config=tests/api-core-tests/playwright.config.js hello.test.js run: pnpm exec playwright test --config=tests/api-core-tests/playwright.config.js hello.test.js
- name: Generate API Test report. - name: Generate API Test report.
@ -80,8 +92,6 @@ jobs:
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with:
ref: ${{ env.BRANCH_NAME }}
- name: Setup WooCommerce Monorepo - name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo uses: ./.github/actions/setup-woocommerce-monorepo
@ -93,13 +103,7 @@ jobs:
working-directory: plugins/woocommerce working-directory: plugins/woocommerce
run: pnpm exec playwright install chromium run: pnpm exec playwright install chromium
- name: Run 'Update WooCommerce' test. - name: Run E2E tests.
working-directory: plugins/woocommerce
env:
UPDATE_WC: true
run: pnpm exec playwright test --config=tests/e2e-pw/daily.playwright.config.js update-woocommerce.spec.js
- name: Run the rest of E2E tests.
timeout-minutes: 60 timeout-minutes: 60
working-directory: plugins/woocommerce working-directory: plugins/woocommerce
env: env:
@ -132,8 +136,6 @@ jobs:
if: success() || failure() if: success() || failure()
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with:
ref: ${{ env.BRANCH_NAME }}
- name: Setup WooCommerce Monorepo - name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo uses: ./.github/actions/setup-woocommerce-monorepo
@ -153,7 +155,7 @@ jobs:
ADMIN_PASSWORD: ${{ secrets.SMOKE_TEST_PERF_ADMIN_PASSWORD }} ADMIN_PASSWORD: ${{ secrets.SMOKE_TEST_PERF_ADMIN_PASSWORD }}
CUSTOMER_USER: ${{ secrets.SMOKE_TEST_PERF_ADMIN_USER }} CUSTOMER_USER: ${{ secrets.SMOKE_TEST_PERF_ADMIN_USER }}
CUSTOMER_PASSWORD: ${{ secrets.SMOKE_TEST_PERF_ADMIN_PASSWORD }} CUSTOMER_PASSWORD: ${{ secrets.SMOKE_TEST_PERF_ADMIN_PASSWORD }}
UPDATE_WC: true UPDATE_WC: nightly
DEFAULT_TIMEOUT_OVERRIDE: 120000 DEFAULT_TIMEOUT_OVERRIDE: 120000
run: | run: |
pnpm exec playwright test --config=tests/e2e-pw/playwright.config.js update-woocommerce.spec.js pnpm exec playwright test --config=tests/e2e-pw/playwright.config.js update-woocommerce.spec.js
@ -204,8 +206,6 @@ jobs:
repo: 'takayukister/contact-form-7' repo: 'takayukister/contact-form-7'
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with:
ref: ${{ env.BRANCH_NAME }}
- name: Setup WooCommerce Monorepo - name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo uses: ./.github/actions/setup-woocommerce-monorepo
@ -269,7 +269,6 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
path: repo path: repo
ref: ${{ env.BRANCH_NAME }}
- name: Download API test report artifact - name: Download API test report artifact
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3

View File

@ -1,185 +1,692 @@
name: Smoke test release name: Smoke test release
on: on:
release:
types: [released, prereleased, published]
workflow_dispatch: workflow_dispatch:
inputs: inputs:
release_id: tag:
description: 'WooCommerce Release Id' description: 'WooCommerce Release Tag'
required: true required: true
concurrency:
group: ${{ github.workflow }}-${{ github.event.release.tag_name || inputs.tag }}
cancel-in-progress: true
permissions: {} permissions: {}
env:
E2E_WP_LATEST_ARTIFACT: e2e-wp-latest--run-${{ github.run_number }}
E2E_UPDATE_WC_ARTIFACT: e2e-update-wc--run-${{ github.run_number }}
FORCE_COLOR: 1
jobs: jobs:
login-run: get-tag:
name: Daily smoke test on release. name: Get WooCommerce release tag
runs-on: ubuntu-20.04
permissions: permissions:
contents: read contents: read
runs-on: ubuntu-20.04
outputs:
tag: ${{ steps.get-tag.outputs.tag }}
created: ${{ steps.created-at.outputs.created }}
steps:
- name: Validate tag
if: ${{ github.event_name == 'workflow_dispatch' }}
env:
GH_TOKEN: ${{ secrets.E2E_GH_TOKEN }}
run: gh release view "${{ inputs.tag }}" --repo=woocommerce/woocommerce
- name: Get tag from triggered event
id: get-tag
env:
RELEASE_TAG: ${{ github.event.release.tag_name || inputs.tag }}
run: |
echo "Triggered event: ${{ github.event_name }}"
echo "Tag from event: $RELEASE_TAG"
echo "tag=$RELEASE_TAG" >> $GITHUB_OUTPUT
- name: Verify woocommerce.zip asset
env:
GH_TOKEN: ${{ secrets.E2E_GH_TOKEN }}
RELEASE_TAG: ${{ steps.get-tag.outputs.tag }}
run: |
ASSET_NAMES=$(gh release view $RELEASE_TAG --repo woocommerce/woocommerce --json assets --jq ".assets[].name")
if [[ $ASSET_NAMES == *"woocommerce.zip"* ]]
then
echo "$RELEASE_TAG has a valid woocommerce.zip asset."
exit 0
fi
echo "$RELEASE_TAG does not have a valid woocommerce.zip asset."
exit 1
- name: Get 'created-at' of WooCommerce zip
id: created-at
env:
GH_TOKEN: ${{ secrets.E2E_GH_TOKEN }}
run: echo "created=$(gh release view ${{ steps.get-tag.outputs.tag }} --json assets --jq .assets[0].createdAt --repo woocommerce/woocommerce)" >> $GITHUB_OUTPUT
e2e-update-wc:
name: Test WooCommerce update
runs-on: ubuntu-20.04
needs: [get-tag]
permissions:
contents: read
env:
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-report
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-results
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with:
ref: trunk
- name: Setup WooCommerce Monorepo - name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo uses: ./.github/actions/setup-woocommerce-monorepo
with: with:
build-filters: woocommerce install-filters: woocommerce
build: false
- name: Install Jest - name: Download and install Chromium browser.
run: npm install -g jest working-directory: plugins/woocommerce
run: pnpm exec playwright install chromium
- name: Run smoke test. - name: Run 'Update WooCommerce' test.
working-directory: plugins/woocommerce working-directory: plugins/woocommerce
env: env:
SMOKE_TEST_URL: ${{ secrets.RELEASE_TEST_URL }} ADMIN_PASSWORD: ${{ secrets.RELEASE_TEST_ADMIN_PASSWORD }}
SMOKE_TEST_ADMIN_USER: ${{ secrets.RELEASE_TEST_ADMIN_USER }} ADMIN_USER: ${{ secrets.RELEASE_TEST_ADMIN_USER }}
SMOKE_TEST_ADMIN_PASSWORD: ${{ secrets.RELEASE_TEST_ADMIN_PASSWORD }} BASE_URL: ${{ secrets.RELEASE_TEST_URL }}
SMOKE_TEST_ADMIN_USER_EMAIL: ${{ secrets.RELEASE_TEST_ADMIN_USER_EMAIL }} CUSTOMER_PASSWORD: ${{ secrets.RELEASE_TEST_CUSTOMER_PASSWORD }}
SMOKE_TEST_CUSTOMER_USER: ${{ secrets.RELEASE_TEST_CUSTOMER_USER }} CUSTOMER_USER: ${{ secrets.RELEASE_TEST_CUSTOMER_USER }}
SMOKE_TEST_CUSTOMER_PASSWORD: ${{ secrets.RELEASE_TEST_CUSTOMER_PASSWORD }}
WC_E2E_SCREENSHOTS: 1
E2E_RETEST: 1
E2E_SLACK_TOKEN: ${{ secrets.SMOKE_TEST_SLACK_TOKEN }}
E2E_SLACK_CHANNEL: 'C02DS4NE72S'
TEST_RELEASE: 1
UPDATE_WC: 1
DEFAULT_TIMEOUT_OVERRIDE: 120000 DEFAULT_TIMEOUT_OVERRIDE: 120000
GITHUB_TOKEN: ${{ secrets.E2E_GH_TOKEN }}
UPDATE_WC: ${{ needs.get-tag.outputs.tag }}
run: |
pnpm exec playwright test \
--config=tests/e2e-pw/playwright.config.js \
update-woocommerce.spec.js
- name: Generate 'Update WooCommerce' test report.
if: success() || failure()
working-directory: plugins/woocommerce
run: pnpm exec allure generate --clean ${{ env.ALLURE_RESULTS_DIR }} --output ${{ env.ALLURE_REPORT_DIR }}
- name: Configure AWS credentials
if: success() || failure()
uses: aws-actions/configure-aws-credentials@v1-node16
with:
aws-region: ${{ secrets.REPORTS_AWS_REGION }}
aws-access-key-id: ${{ secrets.REPORTS_AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.REPORTS_AWS_SECRET_ACCESS_KEY }}
- name: Upload Allure files to bucket
if: success() || failure()
run: |
aws s3 sync ${{ env.ALLURE_RESULTS_DIR }} \
${{ secrets.REPORTS_BUCKET }}/artifacts/${{ github.run_id }}/${{ env.E2E_WP_LATEST_ARTIFACT }}/allure-results \
--quiet
aws s3 sync ${{ env.ALLURE_REPORT_DIR }} \
${{ secrets.REPORTS_BUCKET }}/artifacts/${{ github.run_id }}/${{ env.E2E_WP_LATEST_ARTIFACT }}/allure-report \
--quiet
- name: Publish E2E Allure report
if: success() || failure()
env:
GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }}
ENV_DESCRIPTION: wp-latest
run: |
gh workflow run publish-test-reports-release.yml \
-f created_at="${{ needs.get-tag.outputs.created }}" \
-f run_id=${{ github.run_id }} \
-f run_number=${{ github.run_number }} \
-f release_tag=${{ needs.get-tag.outputs.tag }} \
-f artifact="${{ env.E2E_WP_LATEST_ARTIFACT }}" \
-f env_description="${{ env.ENV_DESCRIPTION }}" \
-f test_type="e2e" \
--repo woocommerce/woocommerce-test-reports
- name: Archive 'Update WooCommerce' test report
if: success() || failure()
uses: actions/upload-artifact@v3
with:
name: ${{ env.E2E_UPDATE_WC_ARTIFACT }}
path: |
${{ env.ALLURE_RESULTS_DIR }}
${{ env.ALLURE_REPORT_DIR }}
if-no-files-found: ignore
retention-days: 5
api-wp-latest:
name: API on WP Latest
runs-on: ubuntu-20.04
needs: [get-tag, e2e-update-wc]
permissions:
contents: read
env:
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/api-test-report/allure-report
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/api-test-report/allure-results
API_WP_LATEST_ARTIFACT: api-wp-latest--run-${{ github.run_number }}
steps:
- uses: actions/checkout@v3
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
install-filters: woocommerce
build: false
- name: Run API tests.
working-directory: plugins/woocommerce
env:
BASE_URL: ${{ secrets.RELEASE_TEST_URL }} BASE_URL: ${{ secrets.RELEASE_TEST_URL }}
USER_KEY: ${{ secrets.RELEASE_TEST_ADMIN_USER }} USER_KEY: ${{ secrets.RELEASE_TEST_ADMIN_USER }}
USER_SECRET: ${{ secrets.RELEASE_TEST_ADMIN_PASSWORD }} USER_SECRET: ${{ secrets.RELEASE_TEST_ADMIN_PASSWORD }}
run: pnpm exec playwright test --config=tests/api-core-tests/playwright.config.js hello
- name: Generate API Test report.
if: success() || failure()
working-directory: plugins/woocommerce
run: pnpm exec allure generate --clean ${{ env.ALLURE_RESULTS_DIR }} --output ${{ env.ALLURE_REPORT_DIR }}
- name: Configure AWS credentials
if: success() || failure()
uses: aws-actions/configure-aws-credentials@v1-node16
with:
aws-region: ${{ secrets.REPORTS_AWS_REGION }}
aws-access-key-id: ${{ secrets.REPORTS_AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.REPORTS_AWS_SECRET_ACCESS_KEY }}
- name: Upload Allure files to bucket
if: success() || failure()
run: | run: |
pnpm exec wc-e2e docker:up aws s3 cp ${{ env.ALLURE_RESULTS_DIR }} \
pnpm exec wc-e2e test:e2e tests/e2e/specs/smoke-tests/update-woocommerce.js ${{ secrets.REPORTS_BUCKET }}/artifacts/${{ github.run_id }}/${{ env.API_WP_LATEST_ARTIFACT }}/allure-results \
pnpm exec wc-e2e test:e2e --recursive \
pnpm exec wc-api-tests test api --quiet
test-wp-version: aws s3 cp ${{ env.ALLURE_REPORT_DIR }} \
name: Smoke test on L-${{ matrix.wp }} WordPress version ${{ secrets.REPORTS_BUCKET }}/artifacts/${{ github.run_id }}/${{ env.API_WP_LATEST_ARTIFACT }}/allure-report \
--recursive \
--quiet
- name: Publish API Allure report
if: success() || failure()
env:
GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }}
ENV_DESCRIPTION: wp-latest
run: |
gh workflow run publish-test-reports-release.yml \
-f created_at="${{ needs.get-tag.outputs.created }}" \
-f run_id=${{ github.run_id }} \
-f run_number=${{ github.run_number }} \
-f release_tag=${{ needs.get-tag.outputs.tag }} \
-f artifact="${{ env.API_WP_LATEST_ARTIFACT }}" \
-f env_description="${{ env.ENV_DESCRIPTION }}" \
-f test_type="api" \
--repo woocommerce/woocommerce-test-reports
- name: Archive API test report
if: success() || failure()
uses: actions/upload-artifact@v3
with:
name: ${{ env.API_WP_LATEST_ARTIFACT }}
path: |
${{ env.ALLURE_RESULTS_DIR }}
${{ env.ALLURE_REPORT_DIR }}
if-no-files-found: ignore
retention-days: 5
e2e-wp-latest:
name: E2E on WP Latest
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
needs: [get-tag, api-wp-latest]
permissions: permissions:
contents: read contents: read
strategy: env:
matrix: ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-report
wp: ['1', '2'] ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-results
steps: steps:
- name: Create dirs.
run: |
mkdir -p package/woocommerce
mkdir -p tmp/woocommerce
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with:
path: package/woocommerce
- name: Setup WooCommerce Monorepo - name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo uses: ./.github/actions/setup-woocommerce-monorepo
with: with:
build-filters: woocommerce install-filters: woocommerce
build: false
- name: Fetch Asset ID - name: Download and install Chromium browser.
id: fetch_asset_id working-directory: plugins/woocommerce
uses: actions/github-script@v5 run: pnpm exec playwright install chromium
- name: Run E2E tests
env: env:
RELEASE_ID: ${{ github.event.inputs.release_id }} ADMIN_PASSWORD: ${{ secrets.RELEASE_TEST_ADMIN_PASSWORD }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ADMIN_USER: ${{ secrets.RELEASE_TEST_ADMIN_USER }}
REPO: ${{ github.repository }} ADMIN_USER_EMAIL: ${{ secrets.RELEASE_TEST_ADMIN_USER_EMAIL }}
BASE_URL: ${{ secrets.RELEASE_TEST_URL }}
CUSTOMER_PASSWORD: ${{ secrets.RELEASE_TEST_CUSTOMER_PASSWORD }}
CUSTOMER_USER: ${{ secrets.RELEASE_TEST_CUSTOMER_USER }}
DEFAULT_TIMEOUT_OVERRIDE: 120000
E2E_MAX_FAILURES: 25
RESET_SITE: true
timeout-minutes: 60
working-directory: plugins/woocommerce
run: pnpm exec playwright test --config=tests/e2e-pw/ignore-plugin-tests.playwright.config.js
- name: Download 'e2e-update-wc' artifact
if: success() || failure()
uses: actions/download-artifact@v3
with: with:
script: | name: ${{ env.E2E_UPDATE_WC_ARTIFACT }}
const script = require( './package/woocommerce/.github/workflows/scripts/fetch-asset-id.js' ) path: plugins/woocommerce/tmp
await script({github, context, core})
- name: Download WooCommerce release zip - name: Add allure-results from 'e2e-update-wc'
working-directory: tmp if: success() || failure()
working-directory: plugins/woocommerce
run: cp -r tmp/allure-results tests/e2e-pw/test-results
- name: Generate E2E Test report.
if: success() || failure()
working-directory: plugins/woocommerce
run: pnpm exec allure generate --clean ${{ env.ALLURE_RESULTS_DIR }} --output ${{ env.ALLURE_REPORT_DIR }}
- name: Configure AWS credentials
if: success() || failure()
uses: aws-actions/configure-aws-credentials@v1-node16
with:
aws-region: ${{ secrets.REPORTS_AWS_REGION }}
aws-access-key-id: ${{ secrets.REPORTS_AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.REPORTS_AWS_SECRET_ACCESS_KEY }}
- name: Upload report to bucket
if: success() || failure()
run: | run: |
curl https://api.github.com/repos/${{ github.repository }}/releases/assets/${{ steps.fetch_asset_id.outputs.asset_id }} -LJOH 'Accept: application/octet-stream' aws s3 sync ${{ env.ALLURE_RESULTS_DIR }} \
${{ secrets.REPORTS_BUCKET }}/artifacts/${{ github.run_id }}/${{ env.E2E_WP_LATEST_ARTIFACT }}/allure-results \
--quiet
aws s3 sync ${{ env.ALLURE_REPORT_DIR }} \
${{ secrets.REPORTS_BUCKET }}/artifacts/${{ github.run_id }}/${{ env.E2E_WP_LATEST_ARTIFACT }}/allure-report \
--quiet
unzip woocommerce.zip -d woocommerce - name: Publish E2E Allure report
rsync -a woocommerce/woocommerce/* ../package/woocommerce/plugins/woocommerce/ if: success() || failure()
- name: Load docker images and start containers.
working-directory: package/woocommerce
env: env:
LATEST_WP_VERSION_MINUS: ${{ matrix.wp }} GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }}
run: pnpm docker:up --filter=woocommerce ENV_DESCRIPTION: wp-latest
run: |
gh workflow run publish-test-reports-release.yml \
-f created_at="${{ needs.get-tag.outputs.created }}" \
-f run_id=${{ github.run_id }} \
-f run_number=${{ github.run_number }} \
-f release_tag=${{ needs.get-tag.outputs.tag }} \
-f artifact="${{ env.E2E_WP_LATEST_ARTIFACT }}" \
-f env_description="${{ env.ENV_DESCRIPTION }}" \
-f test_type="e2e" \
--repo woocommerce/woocommerce-test-reports
- name: Run tests command. - name: Archive E2E test report
working-directory: package/woocommerce/plugins/woocommerce if: success() || failure()
env: uses: actions/upload-artifact@v3
WC_E2E_SCREENSHOTS: 1 with:
E2E_SLACK_TOKEN: ${{ secrets.SMOKE_TEST_SLACK_TOKEN }} name: ${{ env.E2E_WP_LATEST_ARTIFACT }}
E2E_SLACK_CHANNEL: ${{ secrets.RELEASE_TEST_SLACK_CHANNEL }} path: |
run: pnpm exec wc-e2e test:e2e ${{ env.ALLURE_RESULTS_DIR }}
${{ env.ALLURE_REPORT_DIR }}
if-no-files-found: ignore
retention-days: 5
test-plugins: get-wp-versions:
name: Smoke tests with ${{ matrix.plugin }} plugin installed name: Get WP L-1 & L-2 version numbers
needs: [get-tag]
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
permissions: permissions:
contents: read contents: read
strategy: outputs:
fail-fast: false matrix: ${{ steps.get-versions.outputs.versions }}
matrix: tag: ${{ needs.get-tag.outputs.tag }}
include: created: ${{ needs.get-tag.outputs.created }}
- plugin: 'WooCommerce Payments'
repo: 'automattic/woocommerce-payments'
- plugin: 'WooCommerce PayPal Payments'
repo: 'woocommerce/woocommerce-paypal-payments'
- plugin: 'WooCommerce Shipping & Tax'
repo: 'automattic/woocommerce-services'
- plugin: 'WooCommerce Subscriptions'
repo: WC_SUBSCRIPTIONS_REPO
private: true
- plugin: 'WordPress SEO' # Yoast SEO in the UI, but the slug is wordpress-seo
repo: 'Yoast/wordpress-seo'
- plugin: 'Contact Form 7'
repo: 'takayukister/contact-form-7'
steps: steps:
- name: Create dirs. - name: Create dirs
run: | run: |
mkdir -p package/woocommerce mkdir script
mkdir -p tmp/woocommerce mkdir repo
- uses: actions/checkout@v3 - name: Checkout
uses: actions/checkout@v3
with: with:
path: package/woocommerce path: repo
- name: Copy script to get previous WP versions
run: cp repo/plugins/woocommerce/tests/e2e-pw/utils/wordpress.js script
- name: Install axios
working-directory: script
run: npm install axios
- name: Get version numbers
id: get-versions
uses: actions/github-script@v6
with:
script: |
const { getPreviousTwoVersions } = require('./script/wordpress');
const versions = await getPreviousTwoVersions();
console.log(versions);
core.setOutput('versions', versions);
test-wp-versions:
name: Test against ${{ matrix.version.description }} (${{ matrix.version.number }})
runs-on: ubuntu-20.04
needs: [get-wp-versions]
strategy:
matrix: ${{ fromJSON(needs.get-wp-versions.outputs.matrix) }}
env:
API_ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/api/allure-report
API_ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/api/allure-results
API_WP_LATEST_X_ARTIFACT: api-${{ matrix.version.env_description }}--run-${{ github.run_number }}
E2E_ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/e2e/allure-report
E2E_ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/e2e/allure-results
E2E_WP_LATEST_X_ARTIFACT: e2e-${{ matrix.version.env_description }}--run-${{ github.run_number }}
permissions:
contents: read
steps:
- name: Checkout WooCommerce repo
uses: actions/checkout@v3
- name: Setup WooCommerce Monorepo - name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo uses: ./.github/actions/setup-woocommerce-monorepo
with:
build-filters: woocommerce
- name: Fetch Asset ID - name: Launch WP Env
id: fetch_asset_id working-directory: plugins/woocommerce
uses: actions/github-script@v5 run: pnpm run env:test
- name: Download release zip
env: env:
RELEASE_ID: ${{ github.event.inputs.release_id }} GH_TOKEN: ${{ secrets.E2E_GH_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: gh release download ${{ needs.get-wp-versions.outputs.tag }} --dir tmp
REPO: ${{ github.repository }}
with:
script: |
const script = require( './package/woocommerce/.github/workflows/scripts/fetch-asset-id.js' )
await script({github, context, core})
- name: Download WooCommerce release zip - name: Replace `plugins/woocommerce` with unzipped woocommerce release build
working-directory: tmp run: unzip -d plugins -o tmp/woocommerce.zip
- name: Downgrade WordPress version to ${{ matrix.version.number }}
working-directory: plugins/woocommerce
run: | run: |
curl https://api.github.com/repos/${{ github.repository }}/releases/assets/${{ steps.fetch_asset_id.outputs.asset_id }} -LJOH 'Accept: application/octet-stream' pnpm exec wp-env run tests-cli "wp core update --version=${{ matrix.version.number }} --force"
pnpm exec wp-env run tests-cli "wp core update-db"
unzip woocommerce.zip -d woocommerce - name: Verify environment details
rsync -a woocommerce/woocommerce/* ../package/woocommerce/plugins/woocommerce/ working-directory: plugins/woocommerce
- name: Load docker images and start containers.
working-directory: package/woocommerce
env:
LATEST_WP_VERSION_MINUS: ${{ matrix.wp }}
run: pnpm docker:up --filter=woocommerce
- name: Run tests command.
working-directory: package/woocommerce/plugins/woocommerce
env:
WC_E2E_SCREENSHOTS: 1
E2E_SLACK_TOKEN: ${{ secrets.SMOKE_TEST_SLACK_TOKEN }}
E2E_SLACK_CHANNEL: ${{ secrets.RELEASE_TEST_SLACK_CHANNEL }}
PLUGIN_REPOSITORY: ${{ matrix.private && secrets[matrix.repo] || matrix.repo }}
PLUGIN_NAME: ${{ matrix.plugin }}
GITHUB_TOKEN: ${{ secrets.E2E_GH_TOKEN }}
run: | run: |
pnpm exec wc-e2e test:e2e tests/e2e/specs/smoke-tests/upload-plugin.js pnpm exec wp-env run tests-cli "wp core version"
pnpm exec wc-e2e test:e2e pnpm exec wp-env run tests-cli "wp plugin list"
pnpm exec wp-env run tests-cli "wp theme list"
pnpm exec wp-env run tests-cli "wp user list"
- name: Run API tests.
id: api
working-directory: plugins/woocommerce
env:
ALLURE_RESULTS_DIR: ${{ env.API_ALLURE_RESULTS_DIR }}
run: pnpm exec playwright test --config=tests/api-core-tests/playwright.config.js hello
- name: Generate API Allure report.
if: success() || failure()
working-directory: plugins/woocommerce
run: pnpm exec allure generate --clean ${{ env.API_ALLURE_RESULTS_DIR }} --output ${{ env.API_ALLURE_REPORT_DIR }}
- name: Configure AWS credentials
if: success() || failure()
uses: aws-actions/configure-aws-credentials@v1-node16
with:
aws-region: ${{ secrets.REPORTS_AWS_REGION }}
aws-access-key-id: ${{ secrets.REPORTS_AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.REPORTS_AWS_SECRET_ACCESS_KEY }}
- name: Upload API Allure artifacts to bucket
if: success() || failure()
run: |
aws s3 sync ${{ env.API_ALLURE_RESULTS_DIR }} \
${{ secrets.REPORTS_BUCKET }}/artifacts/${{ github.run_id }}/${{ env.API_WP_LATEST_X_ARTIFACT }}/allure-results \
--quiet
aws s3 sync ${{ env.API_ALLURE_REPORT_DIR }} \
${{ secrets.REPORTS_BUCKET }}/artifacts/${{ github.run_id }}/${{ env.API_WP_LATEST_X_ARTIFACT }}/allure-report \
--quiet
- name: Publish API Allure report
if: success() || failure()
env:
GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }}
ENV_DESCRIPTION: ${{ matrix.version.env_description }}
run: |
gh workflow run publish-test-reports-release.yml \
-f created_at="${{ needs.get-wp-versions.outputs.created }}" \
-f run_id=${{ github.run_id }} \
-f run_number=${{ github.run_number }} \
-f release_tag=${{ needs.get-wp-versions.outputs.tag }} \
-f artifact="${{ env.API_WP_LATEST_X_ARTIFACT }}" \
-f env_description="${{ env.ENV_DESCRIPTION }}" \
-f test_type="api" \
--repo woocommerce/woocommerce-test-reports
- name: Archive API Allure reports
if: success() || failure()
uses: actions/upload-artifact@v3
with:
name: ${{ env.API_WP_LATEST_X_ARTIFACT }}
path: |
${{ env.API_ALLURE_RESULTS_DIR }}
${{ env.API_ALLURE_REPORT_DIR }}
if-no-files-found: ignore
retention-days: 5
- name: Download and install Chromium browser.
if: success() || failure()
working-directory: plugins/woocommerce
run: pnpm exec playwright install chromium
- name: Run E2E tests.
if: |
success() ||
( failure() && steps.api.conclusion == 'success' )
timeout-minutes: 60
id: e2e
env:
USE_WP_ENV: 1
E2E_MAX_FAILURES: 15
FORCE_COLOR: 1
ALLURE_RESULTS_DIR: ${{ env.E2E_ALLURE_RESULTS_DIR }}
DEFAULT_TIMEOUT_OVERRIDE: 120000
working-directory: plugins/woocommerce
run: pnpm exec playwright test --config=tests/e2e-pw/playwright.config.js
- name: Generate E2E Allure report.
if: success() || failure()
working-directory: plugins/woocommerce
run: pnpm exec allure generate --clean ${{ env.E2E_ALLURE_RESULTS_DIR }} --output ${{ env.E2E_ALLURE_REPORT_DIR }}
- name: Upload E2E Allure artifacts to bucket
if: success() || failure()
run: |
aws s3 sync ${{ env.E2E_ALLURE_RESULTS_DIR }} \
${{ secrets.REPORTS_BUCKET }}/artifacts/${{ github.run_id }}/${{ env.E2E_WP_LATEST_X_ARTIFACT }}/allure-results \
--quiet
aws s3 sync ${{ env.E2E_ALLURE_REPORT_DIR }} \
${{ secrets.REPORTS_BUCKET }}/artifacts/${{ github.run_id }}/${{ env.E2E_WP_LATEST_X_ARTIFACT }}/allure-report \
--quiet
- name: Archive E2E Allure reports
if: success() || failure()
uses: actions/upload-artifact@v3
with:
name: ${{ env.E2E_WP_LATEST_X_ARTIFACT }}
path: |
${{ env.E2E_ALLURE_RESULTS_DIR }}
${{ env.E2E_ALLURE_REPORT_DIR }}
if-no-files-found: ignore
retention-days: 5
- name: Publish E2E Allure report
if: success() || failure()
env:
GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }}
ENV_DESCRIPTION: ${{ matrix.version.env_description }}
run: |
gh workflow run publish-test-reports-release.yml \
-f created_at="${{ needs.get-wp-versions.outputs.created }}" \
-f run_id=${{ github.run_id }} \
-f run_number=${{ github.run_number }} \
-f release_tag=${{ needs.get-wp-versions.outputs.tag }} \
-f artifact="${{ env.E2E_WP_LATEST_X_ARTIFACT }}" \
-f env_description="${{ env.ENV_DESCRIPTION }}" \
-f test_type="e2e" \
--repo woocommerce/woocommerce-test-reports
test-php-versions:
name: Test against PHP ${{ matrix.php_version }}
runs-on: ubuntu-20.04
needs: [get-tag]
strategy:
matrix:
php_version: ['7.4', '8.1']
env:
API_ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/test-results/allure-report
API_ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/test-results/allure-results
API_ARTIFACT: api-php-${{ matrix.php_version }}--run-${{ github.run_number }}
E2E_ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-report
E2E_ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-results
E2E_ARTIFACT: e2e-php-${{ matrix.php_version }}--run-${{ github.run_number }}
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
- name: Launch WP Env
working-directory: plugins/woocommerce
env:
WP_ENV_PHP_VERSION: ${{ matrix.php_version }}
run: pnpm run env:test
- name: Verify PHP version
working-directory: .github/workflows/scripts
env:
EXPECTED_PHP_VERSION: ${{ matrix.php_version }}
run: bash verify-php-version.sh
- name: Download release zip
env:
GH_TOKEN: ${{ secrets.E2E_GH_TOKEN }}
run: gh release download ${{ needs.get-tag.outputs.tag }} --dir tmp
- name: Replace `plugins/woocommerce` with unzipped woocommerce release build
run: unzip -d plugins -o tmp/woocommerce.zip
- name: Run API tests.
id: api
working-directory: plugins/woocommerce
env:
ALLURE_RESULTS_DIR: ${{ env.API_ALLURE_RESULTS_DIR }}
run: pnpm exec playwright test --config=tests/api-core-tests/playwright.config.js hello
- name: Generate API Allure report.
if: success() || failure()
working-directory: plugins/woocommerce
run: pnpm exec allure generate --clean ${{ env.API_ALLURE_RESULTS_DIR }} --output ${{ env.API_ALLURE_REPORT_DIR }}
- name: Configure AWS credentials
if: success() || failure()
uses: aws-actions/configure-aws-credentials@v1-node16
with:
aws-region: ${{ secrets.REPORTS_AWS_REGION }}
aws-access-key-id: ${{ secrets.REPORTS_AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.REPORTS_AWS_SECRET_ACCESS_KEY }}
- name: Upload API Allure artifacts to bucket
if: success() || failure()
run: |
aws s3 sync ${{ env.API_ALLURE_RESULTS_DIR }} \
${{ secrets.REPORTS_BUCKET }}/artifacts/${{ github.run_id }}/${{ env.API_ARTIFACT }}/allure-results \
--quiet
aws s3 sync ${{ env.API_ALLURE_REPORT_DIR }} \
${{ secrets.REPORTS_BUCKET }}/artifacts/${{ github.run_id }}/${{ env.API_ARTIFACT }}/allure-report \
--quiet
- name: Publish API Allure report
if: success() || failure()
env:
GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }}
ENV_DESCRIPTION: php-${{ matrix.php_version }}
run: |
gh workflow run publish-test-reports-release.yml \
-f created_at="${{ needs.get-tag.outputs.created }}" \
-f run_id=${{ github.run_id }} \
-f run_number=${{ github.run_number }} \
-f release_tag=${{ needs.get-tag.outputs.tag }} \
-f artifact="${{ env.API_ARTIFACT }}" \
-f env_description="${{ env.ENV_DESCRIPTION }}" \
-f test_type="api" \
--repo woocommerce/woocommerce-test-reports
- name: Archive API Allure reports
if: success() || failure()
uses: actions/upload-artifact@v3
with:
name: ${{ env.API_ARTIFACT }}
path: |
${{ env.API_ALLURE_RESULTS_DIR }}
${{ env.API_ALLURE_REPORT_DIR }}
if-no-files-found: ignore
retention-days: 5
- name: Download and install Chromium browser.
working-directory: plugins/woocommerce
run: pnpm exec playwright install chromium
- name: Run E2E tests.
if: |
success() ||
( failure() && steps.api.conclusion == 'success' )
timeout-minutes: 60
env:
USE_WP_ENV: 1
E2E_MAX_FAILURES: 15
FORCE_COLOR: 1
ALLURE_RESULTS_DIR: ${{ env.E2E_ALLURE_RESULTS_DIR }}
DEFAULT_TIMEOUT_OVERRIDE: 120000
working-directory: plugins/woocommerce
run: pnpm exec playwright test --config=tests/e2e-pw/playwright.config.js
- name: Generate E2E Allure report.
if: success() || failure()
working-directory: plugins/woocommerce
run: pnpm exec allure generate --clean ${{ env.E2E_ALLURE_RESULTS_DIR }} --output ${{ env.E2E_ALLURE_REPORT_DIR }}
- name: Upload E2E Allure artifacts to bucket
if: success() || failure()
run: |
aws s3 sync ${{ env.E2E_ALLURE_RESULTS_DIR }} \
${{ secrets.REPORTS_BUCKET }}/artifacts/${{ github.run_id }}/${{ env.E2E_ARTIFACT }}/allure-results \
--quiet
aws s3 sync ${{ env.E2E_ALLURE_REPORT_DIR }} \
${{ secrets.REPORTS_BUCKET }}/artifacts/${{ github.run_id }}/${{ env.E2E_ARTIFACT }}/allure-report \
--quiet
- name: Archive E2E Allure reports
if: success() || failure()
uses: actions/upload-artifact@v3
with:
name: ${{ env.E2E_ARTIFACT }}
path: |
${{ env.E2E_ALLURE_RESULTS_DIR }}
${{ env.E2E_ALLURE_REPORT_DIR }}
if-no-files-found: ignore
retention-days: 5
- name: Publish E2E Allure report
if: success() || failure()
env:
GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }}
ENV_DESCRIPTION: php-${{ matrix.php_version }}
run: |
gh workflow run publish-test-reports-release.yml \
-f created_at="${{ needs.get-tag.outputs.created }}" \
-f run_id=${{ github.run_id }} \
-f run_number=${{ github.run_number }} \
-f release_tag=${{ needs.get-tag.outputs.tag }} \
-f artifact="${{ env.E2E_ARTIFACT }}" \
-f env_description="${{ env.ENV_DESCRIPTION }}" \
-f test_type="e2e" \
--repo woocommerce/woocommerce-test-reports

View File

@ -25,7 +25,7 @@ jobs:
node-version: 16 node-version: 16
- name: 'Install Syncpack' - name: 'Install Syncpack'
run: npm install -g syncpack@^8.2.4 run: npm install -g syncpack@^9.8.4
- name: 'List Mismatches' - name: 'List Mismatches'
run: syncpack list-mismatches run: syncpack list-mismatches

View File

@ -1,6 +1,6 @@
{ {
"dev": true, "dev": true,
"filter": "^(?:react|react-dom|eslint|typescript|@typescript-eslint|@types/react).*$", "filter": "^(?:config|react|react-dom|eslint|typescript|@typescript-eslint|@types/react|@wordpress|@types/wordpress__components|postcss).*$",
"indent": "\t", "indent": "\t",
"overrides": true, "overrides": true,
"peer": true, "peer": true,
@ -33,6 +33,15 @@
"**" "**"
] ]
}, },
{
"dependencies": [
"config"
],
"packages": [
"**"
],
"pinVersion": "3.3.7"
},
{ {
"dependencies": [ "dependencies": [
"react", "react",
@ -63,6 +72,88 @@
"**" "**"
], ],
"pinVersion": "^8.32.0" "pinVersion": "^8.32.0"
},
{
"dependencies": [
"@wordpress/eslint-plugin",
"@wordpress/babel-plugin-import-jsx-pragma",
"@wordpress/babel-preset-default",
"@wordpress/env",
"@wordpress/stylelint-config",
"@wordpress/prettier-config",
"@wordpress/scripts",
"@wordpress/jest-console",
"@wordpress/dependency-extraction-webpack-plugin",
"@wordpress/e2e-test-utils",
"@wordpress/jest-preset-default",
"@wordpress/postcss-plugins-preset",
"@wordpress/custom-templated-path-webpack-plugin",
"@wordpress/postcss-themes"
],
"packages": [
"**"
],
"isIgnored": true
},
{
"dependencies": [
"@wordpress/block**",
"@wordpress/viewport"
],
"packages": [
"@woocommerce/product-editor",
"woocommerce/client/admin",
"@woocommerce/components"
],
"isIgnored": true
},
{
"dependencies": [
"@wordpress/**"
],
"packages": [
"@woocommerce/experimental"
],
"isIgnored": true
},
{
"dependencies": [
"@wordpress/**"
],
"packages": [
"**"
],
"pinVersion": "wp-6.0"
},
{
"dependencies": [
"@types/wordpress__components"
],
"packages": [
"**"
],
"pinVersion": "^19.10.3"
},
{
"dependencies": [
"postcss-loader"
],
"dependencyTypes": [
"devDependencies"
],
"packages": [
"**"
],
"pinVersion": "^4.3.0"
},
{
"dependencies": [
"postcss"
],
"packages": [
"**"
],
"pinVersion": "^8.4.7"
} }
] ]
} }

View File

@ -27,12 +27,13 @@ pnpm exec syncpack -- list-mismatches
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo "You must sync the dependencies listed above before you can push this branch." echo "You must sync the dependencies listed above before you can push this branch."
echo "This can usually be accomplished automatically by updating the pinned version in `.syncpackrc` and then running \`pnpm run sync-dependencies\`." echo "This can usually be accomplished automatically by updating the pinned version in \`.syncpackrc\` and then running \`pnpm run sync-dependencies\`."
exit 1 exit 1
fi fi
# Ensure both branches are tracked or check-changelogger-use will fail. # Ensure both branches are tracked or check-changelogger-use will fail. Note we pass hooksPath
git checkout $PROTECTED_BRANCH --quiet # to avoid running the pre-commit hook.
git checkout $CURRENT_BRANCH --quiet git -c core.hooksPath=/dev/null checkout $PROTECTED_BRANCH --quiet
git -c core.hooksPath=/dev/null checkout $CURRENT_BRANCH --quiet
php tools/monorepo/check-changelogger-use.php $PROTECTED_BRANCH $CURRENT_BRANCH php tools/monorepo/check-changelogger-use.php $PROTECTED_BRANCH $CURRENT_BRANCH

View File

@ -1,5 +1,145 @@
== Changelog == == Changelog ==
= 7.4.1 2023-03-01 =
**WooCommerce**
* Update - Update WooCommerce Blocks to 9.4.4. [#36982](https://github.com/woocommerce/woocommerce/pull/36982)
= 7.4.0 2023-02-14 =
**WooCommerce**
* Fix - Add support for sorting by includes param. [#36215](https://github.com/woocommerce/woocommerce/pull/36215)
* Fix - Allow product tab navigation without prompting for unsaved changes [#36235](https://github.com/woocommerce/woocommerce/pull/36235)
* Fix - Convert HTML to blocks in product variation description [#36241](https://github.com/woocommerce/woocommerce/pull/36241)
* Fix - Decode HTML entities in CategoryBreadcrumbs. [#36321](https://github.com/woocommerce/woocommerce/pull/36321)
* Fix - Decode HTML entities in CategoryFieldItem. [#36367](https://github.com/woocommerce/woocommerce/pull/36367)
* Fix - Ensure order emails are responsive in most email clients, including when the current language is RTL. [#36310](https://github.com/woocommerce/woocommerce/pull/36310)
* Fix - Ensures product variation sort order is correctly persisted. [#36343](https://github.com/woocommerce/woocommerce/pull/36343)
* Fix - Ensure wc_get_order() works without arguments when HPOS is enabled. [#36496](https://github.com/woocommerce/woocommerce/pull/36496)
* Fix - Fix "Save changes?" modal saves the options after selecting the 'Discard' option [#36160](https://github.com/woocommerce/woocommerce/pull/36160)
* Fix - Fix attributes/options lists corrupt render #36236 [#36236](https://github.com/woocommerce/woocommerce/pull/36236)
* Fix - Fix bug when filtering for customer_id=0. [#36216](https://github.com/woocommerce/woocommerce/pull/36216)
* Fix - Fix deprecated usage of ${var} in strings [#36439](https://github.com/woocommerce/woocommerce/pull/36439)
* Fix - Fix edit attribute modal terms list [#36186](https://github.com/woocommerce/woocommerce/pull/36186)
* Fix - Fixes editing of child product reviews. [#35888](https://github.com/woocommerce/woocommerce/pull/35888)
* Fix - Fix for product filters when 'shop' page is the front page. [#36224](https://github.com/woocommerce/woocommerce/pull/36224)
* Fix - Fix issue where attribute term dropdown was not adhering to sort order setting. [#36047](https://github.com/woocommerce/woocommerce/pull/36047)
* Fix - Fix navigation between variations and tab selection [#36239](https://github.com/woocommerce/woocommerce/pull/36239)
* Fix - Fix notices styling in Twenty Twenty-Three [#36475](https://github.com/woocommerce/woocommerce/pull/36475)
* Fix - Fix overlapping header elements on product page [#36495](https://github.com/woocommerce/woocommerce/pull/36495)
* Fix - Fix product table dropdown issue on mobile. [#36046](https://github.com/woocommerce/woocommerce/pull/36046)
* Fix - Fix reordering list items error [#36296](https://github.com/woocommerce/woocommerce/pull/36296)
* Fix - Fix REST API order refunds enpoint when HPOS is active, and make v2 orders endpoint compatible with HPOS [#36308](https://github.com/woocommerce/woocommerce/pull/36308)
* Fix - Fix settings tables styles [#36531](https://github.com/woocommerce/woocommerce/pull/36531)
* Fix - Fix tax task showing as not completed after setting up tax [#36468](https://github.com/woocommerce/woocommerce/pull/36468)
* Fix - Fix the signature mismatch affecting wc cli commands ability to fetch user subscription data. [#36240](https://github.com/woocommerce/woocommerce/pull/36240)
* Fix - Fix total count query of orders within Analytics reports data store. [#35971](https://github.com/woocommerce/woocommerce/pull/35971)
* Fix - Hide Variations section when it is empty [#36202](https://github.com/woocommerce/woocommerce/pull/36202)
* Fix - Improve accessibility of the coupon code label, in the context of the cart page. [#36247](https://github.com/woocommerce/woocommerce/pull/36247)
* Fix - Improve the way we retrieve the alt text property for product attachments. [#35009](https://github.com/woocommerce/woocommerce/pull/35009)
* Fix - Load wc_empty_cart function for REST API calls. [#36182](https://github.com/woocommerce/woocommerce/pull/36182)
* Fix - Make HPOS UX more consistent with posts UI (so that same e2e tests passes for both). [#36282](https://github.com/woocommerce/woocommerce/pull/36282)
* Fix - Make order edit messages compatible with both posts and theorder object. [#36485](https://github.com/woocommerce/woocommerce/pull/36485)
* Fix - Make sure the tracking shortcode only operates in orders with billing information. [#33735](https://github.com/woocommerce/woocommerce/pull/33735)
* Fix - Remove persisted query on return to parent product from variation [#36365](https://github.com/woocommerce/woocommerce/pull/36365)
* Fix - Reset variation form if a new variation is given [#36078](https://github.com/woocommerce/woocommerce/pull/36078)
* Fix - Restore the pre-7.2.0 behavior for single product quantity inputs. [#36460](https://github.com/woocommerce/woocommerce/pull/36460)
* Fix - Set child orders to be children of current order parent before deleting for consistency. [#36218](https://github.com/woocommerce/woocommerce/pull/36218)
* Fix - Skip custom search for HPOS API queries as it's handled already. [#36213](https://github.com/woocommerce/woocommerce/pull/36213)
* Fix - Use Imagick functions to set parallel thread count instead of direct putenv call as suggested in https://core.trac.wordpress.org/ticket/36534#comment:129. [#35339](https://github.com/woocommerce/woocommerce/pull/35339)
* Fix - When adjusting download permissions, confirm the child products have not been removed. [#36431](https://github.com/woocommerce/woocommerce/pull/36431)
* Add - Add ability to filter variations by local attributes in REST API [#36201](https://github.com/woocommerce/woocommerce/pull/36201)
* Add - Add an admin notice about the upcoming PHP version requirement change for PHP 7.2 users [#36444](https://github.com/woocommerce/woocommerce/pull/36444)
* Add - Added a slot for extending the app with a homescreen header banner [#36467](https://github.com/woocommerce/woocommerce/pull/36467)
* Add - Added a slot for ProgressHeader and ProgressTitle component [#36482](https://github.com/woocommerce/woocommerce/pull/36482)
* Add - Add edit button to variations list items [#36079](https://github.com/woocommerce/woocommerce/pull/36079)
* Add - Added slot for tasklist completion slotfill [#36487](https://github.com/woocommerce/woocommerce/pull/36487)
* Add - Add endpoint to create all product variations [#35980](https://github.com/woocommerce/woocommerce/pull/35980)
* Add - Add exit prompt CES for users editing orders when tracking is enabled. [#35762](https://github.com/woocommerce/woocommerce/pull/35762)
* Add - Adding delayed spotlight to feedback button on current product page. [#35865](https://github.com/woocommerce/woocommerce/pull/35865)
* Add - Adding feedback button to activity bar on classic product page. [#35810](https://github.com/woocommerce/woocommerce/pull/35810)
* Add - Adding JS data store for ProductForm. [#36430](https://github.com/woocommerce/woocommerce/pull/36430)
* Add - Adding the WooProductSectionItem slot within the product editor general tab. [#36331](https://github.com/woocommerce/woocommerce/pull/36331)
* Add - Add initial product form PHP helper class to add new fields. [#36093](https://github.com/woocommerce/woocommerce/pull/36093)
* Add - Additional error logging within the CSV Exporter framework. [#34802](https://github.com/woocommerce/woocommerce/pull/34802)
* Add - Add multichannel marketing API [#36453](https://github.com/woocommerce/woocommerce/pull/36453)
* Add - Add new filter to add additional clauses for SQL statement in Variations report [#36378](https://github.com/woocommerce/woocommerce/pull/36378)
* Add - Add new product form API for extending the new Product Form MVP. [#36165](https://github.com/woocommerce/woocommerce/pull/36165)
* Add - Add Options section to new product experience form. [#35910](https://github.com/woocommerce/woocommerce/pull/35910)
* Add - Add product tour to new product management experience [#36428](https://github.com/woocommerce/woocommerce/pull/36428)
* Add - Add product variation form [#36033](https://github.com/woocommerce/woocommerce/pull/36033)
* Add - Add product variation General section [#36081](https://github.com/woocommerce/woocommerce/pull/36081)
* Add - Add product variation header actions and persistence [#36155](https://github.com/woocommerce/woocommerce/pull/36155)
* Add - Add product variation image [#36133](https://github.com/woocommerce/woocommerce/pull/36133)
* Add - Add product variation navigation component [#36076](https://github.com/woocommerce/woocommerce/pull/36076)
* Add - Add product variations flag to only show work in development [#36311](https://github.com/woocommerce/woocommerce/pull/36311)
* Add - Add product variation title to page header [#36085](https://github.com/woocommerce/woocommerce/pull/36085)
* Add - Add Product variation visibility toggle [#36020](https://github.com/woocommerce/woocommerce/pull/36020)
* Add - Add single product variation sections [#36051](https://github.com/woocommerce/woocommerce/pull/36051)
* Add - Adds support for a 'required' argument when invoking `wc_dropdown_variation_attribute_options()`. [#34579](https://github.com/woocommerce/woocommerce/pull/34579)
* Add - Add support for sorting by order metadata in HPOS queries. [#36403](https://github.com/woocommerce/woocommerce/pull/36403)
* Add - Add WooOnboardingTaskListHeader, woocommerce_admin_experimental_onboarding_tasklists filter, and woocommerce_onboarding_task_list_header Slot to task list [#36519](https://github.com/woocommerce/woocommerce/pull/36519)
* Add - Include tax options in pricing section [#36299](https://github.com/woocommerce/woocommerce/pull/36299)
* Add - Persist active tab on refresh [#36112](https://github.com/woocommerce/woocommerce/pull/36112)
* Add - Persist variations order on product save [#36109](https://github.com/woocommerce/woocommerce/pull/36109)
* Add - Product variation quantity status indicator [#35982](https://github.com/woocommerce/woocommerce/pull/35982)
* Add - Product variations card should have a fixed height. [#36053](https://github.com/woocommerce/woocommerce/pull/36053)
* Add - Remove manage_stock 'parent' value before saving the variation [#36234](https://github.com/woocommerce/woocommerce/pull/36234)
* Add - Run ces exit prompt when product import abandoned. [#35996](https://github.com/woocommerce/woocommerce/pull/35996)
* Add - Scroll newly added product attribute into view in new product management experience [#36447](https://github.com/woocommerce/woocommerce/pull/36447)
* Add - Show product CES footer on product tour close [#36516](https://github.com/woocommerce/woocommerce/pull/36516)
* Add - Truncate attribute option name to a max of 32 chars in variations list [#36134](https://github.com/woocommerce/woocommerce/pull/36134)
* Add - Trying experimental slot context with product editor fills. [#36333](https://github.com/woocommerce/woocommerce/pull/36333)
* Add - Using slotfill to insert attributes section in the product editor. [#36483](https://github.com/woocommerce/woocommerce/pull/36483)
* Add - Using slotfill to insert images section in product editor. [#36461](https://github.com/woocommerce/woocommerce/pull/36461)
* Update - Update woocommerce-blocks to 9.4.3. [#36736](https://github.com/woocommerce/woocommerce/pull/36736)
* Update - Adding WooProductFieldItem slot to product details section. [#36315](https://github.com/woocommerce/woocommerce/pull/36315)
* Update - Add permalink_template and generated_slug to products REST API response. [#36497](https://github.com/woocommerce/woocommerce/pull/36497)
* Update - Auto generate variations on option changes [#36188](https://github.com/woocommerce/woocommerce/pull/36188)
* Update - Bundled version of Action Scheduler updated to 3.5.4. [#36433](https://github.com/woocommerce/woocommerce/pull/36433)
* Update - Customers REST API endpoint will now return user metadata only when requester has an administrator role [#36408](https://github.com/woocommerce/woocommerce/pull/36408)
* Update - Disable irrelevant product tabs when variations exist [#35939](https://github.com/woocommerce/woocommerce/pull/35939)
* Update - Migrate shipping section in product editor to slot fill. [#36534](https://github.com/woocommerce/woocommerce/pull/36534)
* Update - Move product management feature flag down to experimental. [#36552](https://github.com/woocommerce/woocommerce/pull/36552)
* Update - Reimplementing product details fields in product editor as slot fills. [#36368](https://github.com/woocommerce/woocommerce/pull/36368)
* Update - Update api-core-tests readme to include a guide for writing tests [#35978](https://github.com/woocommerce/woocommerce/pull/35978)
* Update - Update store-details test snapshot to reflect updated select-control [#35808](https://github.com/woocommerce/woocommerce/pull/35808)
* Update - Update WooCommerce Blocks to 9.4.0 [#36524](https://github.com/woocommerce/woocommerce/pull/36524)
* Update - Update WooCommerce Blocks to 9.4.1 [#36553](https://github.com/woocommerce/woocommerce/pull/36553)
* Update - Update WooCommerce Blocks to 9.4.2 [#36624](https://github.com/woocommerce/woocommerce/pull/36624)
* Dev - Add advanced setting option [#36380](https://github.com/woocommerce/woocommerce/pull/36380)
* Dev - Add experimental SlotFill for task list footer [#36527](https://github.com/woocommerce/woocommerce/pull/36527)
* Dev - Cleanup product task experiment [#35950](https://github.com/woocommerce/woocommerce/pull/35950)
* Dev - Consistent folder structure for E2E and API test results [#35907](https://github.com/woocommerce/woocommerce/pull/35907)
* Dev - Fix docblock type annotations for $meta_value. [#33853](https://github.com/woocommerce/woocommerce/pull/33853)
* Dev - Fix flakiness of the `can save industry changes when navigating back to "Store Details"` E2E test. [#36260](https://github.com/woocommerce/woocommerce/pull/36260)
* Dev - Make shopper tests passable on daily smoke test site. [#35873](https://github.com/woocommerce/woocommerce/pull/35873)
* Dev - Move product attribute fetching logic into a separate hook [#36354](https://github.com/woocommerce/woocommerce/pull/36354)
* Dev - Update TaskLists::add_task() to reflect changes in TaskList::add_task() [#36104](https://github.com/woocommerce/woocommerce/pull/36104)
* Dev - Update the browserslist config for legacy client JS to match Wordpress. [#36264](https://github.com/woocommerce/woocommerce/pull/36264)
* Dev - Upgrade PHPUnit to v8 [#36273](https://github.com/woocommerce/woocommerce/pull/36273)
* Tweak - Corrects a typo in the i18n/states.php file, relating to our list of Iranian states. [#36457](https://github.com/woocommerce/woocommerce/pull/36457)
* Tweak - Derive product type from product attributes [#36243](https://github.com/woocommerce/woocommerce/pull/36243)
* Tweak - Fix typo in a function comment. [#36122](https://github.com/woocommerce/woocommerce/pull/36122)
* Tweak - Fix units in function doc comment [#36353](https://github.com/woocommerce/woocommerce/pull/36353)
* Tweak - Make related products check more robust against wrong transients. [#34742](https://github.com/woocommerce/woocommerce/pull/34742)
* Tweak - Makes it possible to use an `add_meta_boxes_<SCREEN_ID>` style hook in the HPOS editor, for parity with the traditional post editor. [#35999](https://github.com/woocommerce/woocommerce/pull/35999)
* Tweak - Minor adjustments to the ProductForm API [#36414](https://github.com/woocommerce/woocommerce/pull/36414)
* Tweak - Redirect to new product experience when in experiment group [#36381](https://github.com/woocommerce/woocommerce/pull/36381)
* Tweak - Refactor AttributeField into sub-components. [#35997](https://github.com/woocommerce/woocommerce/pull/35997)
* Tweak - Update product links when new product management experience is enabled [#36382](https://github.com/woocommerce/woocommerce/pull/36382)
* Tweak - Updates and improves the docblocks for methods WC_Order::get_total() and WC_Order::get_subtotal(). [#34385](https://github.com/woocommerce/woocommerce/pull/34385)
* Tweak - Validation of Norweigan postcodes has been added. [#36277](https://github.com/woocommerce/woocommerce/pull/36277)
* Performance - Speed up HPOS search query by using group by instead of distinct. [#35897](https://github.com/woocommerce/woocommerce/pull/35897)
* Enhancement - Add context to countries shipping to prefix [#36254](https://github.com/woocommerce/woocommerce/pull/36254)
* Enhancement - Adds new order status filters for bacs and cheque email instructions. [#35849](https://github.com/woocommerce/woocommerce/pull/35849)
* Enhancement - Improves handling of the single product page quantity selector, in relation to variable products. [#36087](https://github.com/woocommerce/woocommerce/pull/36087)
* Enhancement - Remove default WooCommerce button styles if using a block theme which adds button styles in theme.json [#36225](https://github.com/woocommerce/woocommerce/pull/36225)
= 7.3.0 2023-01-10 = = 7.3.0 2023-01-10 =
**WooCommerce** **WooCommerce**

4
changelog/pr-36705 Normal file
View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Added `woocommerce_widget_layered_nav_filters_start/end` hooks around layered nav filters widget

View File

@ -29,7 +29,7 @@
"@babel/runtime": "^7.17.2", "@babel/runtime": "^7.17.2",
"@types/node": "14.14.33", "@types/node": "14.14.33",
"@woocommerce/eslint-plugin": "workspace:*", "@woocommerce/eslint-plugin": "workspace:*",
"@wordpress/data": "^6.15.0", "@wordpress/data": "wp-6.0",
"@wordpress/eslint-plugin": "^11.1.0", "@wordpress/eslint-plugin": "^11.1.0",
"@wordpress/prettier-config": "^1.1.1", "@wordpress/prettier-config": "^1.1.1",
"babel-loader": "^8.2.3", "babel-loader": "^8.2.3",
@ -44,13 +44,13 @@
"mkdirp": "^1.0.4", "mkdirp": "^1.0.4",
"moment": "^2.29.1", "moment": "^2.29.1",
"node-stream-zip": "^1.15.0", "node-stream-zip": "^1.15.0",
"postcss-loader": "^3.0.0", "postcss-loader": "^4.3.0",
"prettier": "npm:wp-prettier@^2.2.1-beta-1", "prettier": "npm:wp-prettier@^2.2.1-beta-1",
"regenerator-runtime": "^0.13.9", "regenerator-runtime": "^0.13.9",
"request": "^2.88.2", "request": "^2.88.2",
"sass": "^1.49.9", "sass": "^1.49.9",
"sass-loader": "^10.2.1", "sass-loader": "^10.2.1",
"syncpack": "^8.3.9", "syncpack": "^9.8.4",
"turbo": "^1.7.0", "turbo": "^1.7.0",
"typescript": "^4.8.3", "typescript": "^4.8.3",
"url-loader": "^1.1.2", "url-loader": "^1.1.2",
@ -62,5 +62,11 @@
"@wordpress/babel-preset-default": "^6.4.1", "@wordpress/babel-preset-default": "^6.4.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"wp-textdomain": "1.0.1" "wp-textdomain": "1.0.1"
},
"pnpm": {
"overrides": {
"@types/react": "^17.0.2",
"react": "^17.0.2"
}
} }
} }

View File

@ -62,4 +62,12 @@ To create a new package, add a new folder to `/packages`, containing…
- Usage example - Usage example
4. A `src` directory for the source of your module, which will be built by default using the `pnpm run turbo:build` command. Note that you'll want an `index.js` file that exports the package contents, see other packages for examples. 4. A `src` directory for the source of your module, which will be built by default using the `pnpm run turbo:build` command. Note that you'll want an `index.js` file that exports the package contents, see other packages for examples.
5. Add the new package name to `packages/js/dependency-extraction-webpack-plugin/assets/packages.js` so that users of that plugin will also be able to use the new package without enqueuing it. 5. A blank Changelog file, `changelog.md`.
```
# Changelog
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
```
6. Add the new package name to `packages/js/dependency-extraction-webpack-plugin/assets/packages.js` so that users of that plugin will also be able to use the new package without enqueuing it.

View File

@ -0,0 +1,4 @@
Significance: minor
Type: dev
Use syncpack to update dependencies.

View File

@ -0,0 +1,4 @@
Significance: minor
Type: dev
Lint fixes

View File

@ -0,0 +1,4 @@
Significance: patch
Type: fix
Unify semver range for `config@3.3.7` (from `^3.3.7`). Fix `node_env_var_name is not defined` error.

View File

@ -29,7 +29,7 @@
"@jest/globals": "^27.5.1", "@jest/globals": "^27.5.1",
"@types/jest": "^27.4.1", "@types/jest": "^27.4.1",
"@woocommerce/e2e-utils": "workspace:*", "@woocommerce/e2e-utils": "workspace:*",
"config": "^3.3.7" "config": "3.3.7"
}, },
"peerDependencies": { "peerDependencies": {
"@woocommerce/e2e-environment": "^0.2.3 || ^0.3.0", "@woocommerce/e2e-environment": "^0.2.3 || ^0.3.0",
@ -41,7 +41,7 @@
"@types/config": "0.0.41", "@types/config": "0.0.41",
"@types/expect-puppeteer": "^4.4.7", "@types/expect-puppeteer": "^4.4.7",
"@types/puppeteer": "^5.4.5", "@types/puppeteer": "^5.4.5",
"@typescript-eslint/eslint-plugin": "^5.43.0", "@typescript-eslint/eslint-plugin": "^5.54.0",
"@woocommerce/api": "^0.2.0", "@woocommerce/api": "^0.2.0",
"@woocommerce/eslint-plugin": "workspace:*", "@woocommerce/eslint-plugin": "workspace:*",
"eslint": "^8.32.0", "eslint": "^8.32.0",

View File

@ -1,7 +1,7 @@
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { waitForElementByText, getElementByText } from '../utils/actions'; import { waitForElementByText } from '../utils/actions';
import { BasePage } from './BasePage'; import { BasePage } from './BasePage';
type PaymentMethodWithSetupButton = type PaymentMethodWithSetupButton =
@ -12,7 +12,7 @@ type PaymentMethodWithSetupButton =
| 'mollie' | 'mollie'
| 'bacs'; | 'bacs';
type PaymentMethod = PaymentMethodWithSetupButton | 'cod'; // type PaymentMethod = PaymentMethodWithSetupButton | 'cod';
export class PaymentsSetup extends BasePage { export class PaymentsSetup extends BasePage {
url = 'wp-admin/admin.php?page=wc-admin&task=payments'; url = 'wp-admin/admin.php?page=wc-admin&task=payments';

View File

@ -0,0 +1,4 @@
Significance: minor
Type: dev
Syncpack update of @typescript/eslint dependencies.

View File

@ -51,8 +51,8 @@
"@types/create-hmac": "1.1.0", "@types/create-hmac": "1.1.0",
"@types/jest": "^27.4.1", "@types/jest": "^27.4.1",
"@types/node": "13.13.5", "@types/node": "13.13.5",
"@typescript-eslint/eslint-plugin": "^5.43.0", "@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.43.0", "@typescript-eslint/parser": "^5.54.0",
"@woocommerce/eslint-plugin": "workspace:*", "@woocommerce/eslint-plugin": "workspace:*",
"axios-mock-adapter": "^1.20.0", "axios-mock-adapter": "^1.20.0",
"eslint": "^8.32.0", "eslint": "^8.32.0",

View File

@ -1,6 +1,7 @@
module.exports = { module.exports = {
extends: [ 'plugin:@woocommerce/eslint-plugin/recommended' ], extends: [ 'plugin:@woocommerce/eslint-plugin/recommended' ],
root: true, root: true,
ignorePatterns: [ '**/test/*.ts', '**/test/*.tsx' ],
overrides: [ overrides: [
{ {
files: [ files: [

View File

@ -0,0 +1,4 @@
Significance: minor
Type: dev
Add a11y support for the Tree component

View File

@ -0,0 +1,4 @@
Significance: minor
Type: dev
Add custom rendering logic to the item label

View File

@ -0,0 +1,4 @@
Significance: minor
Type: dev
Add highlighter to the tree control

View File

@ -0,0 +1,4 @@
Significance: minor
Type: dev
Add selection logic to tree control component

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Added LearnMore option as well as made it possible to use this button multiple instances on the page

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Fix dependency versions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: dev
Sync @wordpress package versions via syncpack.

View File

@ -0,0 +1,4 @@
Significance: minor
Type: dev
Adjust eslintrc for changes to eslint plugin.

View File

@ -0,0 +1,4 @@
Significance: minor
Type: fix
Refactor createOrderedChildren

View File

@ -0,0 +1,4 @@
Significance: patch
Type: fix
Fix issue were Options tab was not showing up anymore in new product management screen.

View File

@ -0,0 +1,4 @@
Significance: patch
Type: fix
Fix SelectControl and TreeControl styles.

View File

@ -0,0 +1,4 @@
Significance: minor
Type: update
Export TreeSelectControl component and add additional props: onInputChange, alwaysShowPlaceholder, includeParent.

View File

@ -0,0 +1,4 @@
Significance: minor
Type: update
Add deprecated message to product slot fill components

View File

@ -0,0 +1,4 @@
Significance: minor
Type: update
Add deprecated message to packages moved to product-editor package.

View File

@ -0,0 +1,4 @@
Significance: minor
Type: tweak
Small tweak to update reference to currencyContext component.

View File

@ -37,33 +37,34 @@
"@types/wordpress__block-library": "^2.6.1", "@types/wordpress__block-library": "^2.6.1",
"@types/wordpress__blocks": "^11.0.7", "@types/wordpress__blocks": "^11.0.7",
"@types/wordpress__rich-text": "^3.4.6", "@types/wordpress__rich-text": "^3.4.6",
"@types/wordpress__components": "^19.10.3",
"@woocommerce/csv-export": "workspace:*", "@woocommerce/csv-export": "workspace:*",
"@woocommerce/currency": "workspace:*", "@woocommerce/currency": "workspace:*",
"@woocommerce/data": "workspace:*", "@woocommerce/data": "workspace:*",
"@woocommerce/date": "workspace:*", "@woocommerce/date": "workspace:*",
"@woocommerce/navigation": "workspace:*", "@woocommerce/navigation": "workspace:*",
"@wordpress/a11y": "3.5.0", "@wordpress/a11y": "wp-6.0",
"@wordpress/api-fetch": "^6.0.1", "@wordpress/api-fetch": "wp-6.0",
"@wordpress/base-styles": "^4.3.0", "@wordpress/base-styles": "wp-6.0",
"@wordpress/block-editor": "^9.8.0", "@wordpress/block-editor": "^9.8.0",
"@wordpress/block-library": "^7.16.0", "@wordpress/block-library": "^7.16.0",
"@wordpress/blocks": "^11.18.0", "@wordpress/blocks": "^11.18.0",
"@wordpress/components": "^19.5.0", "@wordpress/components": "wp-6.0",
"@wordpress/compose": "^5.1.2", "@wordpress/compose": "wp-6.0",
"@wordpress/core-data": "^4.2.1", "@wordpress/core-data": "wp-6.0",
"@wordpress/date": "^4.3.1", "@wordpress/date": "wp-6.0",
"@wordpress/deprecated": "^3.3.1", "@wordpress/deprecated": "wp-6.0",
"@wordpress/dom": "^3.3.2", "@wordpress/dom": "wp-6.0",
"@wordpress/element": "^4.1.1", "@wordpress/element": "wp-6.0",
"@wordpress/hooks": "^3.5.0", "@wordpress/hooks": "wp-6.0",
"@wordpress/html-entities": "^3.3.1", "@wordpress/html-entities": "wp-6.0",
"@wordpress/i18n": "^4.3.1", "@wordpress/i18n": "wp-6.0",
"@wordpress/icons": "^8.1.0", "@wordpress/icons": "wp-6.0",
"@wordpress/keyboard-shortcuts": "^3.17.0", "@wordpress/keyboard-shortcuts": "wp-6.0",
"@wordpress/keycodes": "^3.3.1", "@wordpress/keycodes": "wp-6.0",
"@wordpress/media-utils": "^4.6.0", "@wordpress/media-utils": "wp-6.0",
"@wordpress/rich-text": "^5.17.0", "@wordpress/rich-text": "wp-6.0",
"@wordpress/url": "^3.4.1", "@wordpress/url": "wp-6.0",
"@wordpress/viewport": "^4.1.2", "@wordpress/viewport": "^4.1.2",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"core-js": "^3.21.1", "core-js": "^3.21.1",
@ -86,8 +87,10 @@
"react-transition-group": "^4.4.2" "react-transition-group": "^4.4.2"
}, },
"peerDependencies": { "peerDependencies": {
"@wordpress/data": "^6.2.1", "@wordpress/data": "wp-6.0",
"lodash": "^4.17.0", "lodash": "^4.17.0",
"@types/react": "^17.0.2",
"@types/react-dom": "^17.0.2",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2" "react-dom": "^17.0.2"
}, },
@ -117,23 +120,23 @@
"@types/jest": "^27.4.1", "@types/jest": "^27.4.1",
"@types/lodash": "^4.14.184", "@types/lodash": "^4.14.184",
"@types/prop-types": "^15.7.4", "@types/prop-types": "^15.7.4",
"@types/react": "^17.0.2",
"@types/testing-library__jest-dom": "^5.14.3", "@types/testing-library__jest-dom": "^5.14.3",
"@types/uuid": "^8.3.0", "@types/uuid": "^8.3.0",
"@types/wordpress__components": "^19.10.1", "@types/wordpress__components": "^19.10.3",
"@types/wordpress__data": "^6.0.0", "@types/wordpress__data": "^6.0.0",
"@types/wordpress__media-utils": "^3.0.0", "@types/wordpress__media-utils": "^3.0.0",
"@types/wordpress__viewport": "^2.5.4", "@types/wordpress__viewport": "^2.5.4",
"@woocommerce/eslint-plugin": "workspace:*", "@woocommerce/eslint-plugin": "workspace:*",
"@woocommerce/internal-style-build": "workspace:*", "@woocommerce/internal-style-build": "workspace:*",
"@wordpress/browserslist-config": "^4.1.1", "@wordpress/browserslist-config": "wp-6.0",
"@wordpress/scripts": "^12.6.1", "@wordpress/scripts": "^12.6.1",
"concurrently": "^7.0.0", "concurrently": "^7.0.0",
"css-loader": "^3.6.0", "css-loader": "^3.6.0",
"eslint": "^8.32.0", "eslint": "^8.32.0",
"jest": "^27.5.1", "jest": "^27.5.1",
"jest-cli": "^27.5.1", "jest-cli": "^27.5.1",
"postcss-loader": "^3.0.0", "postcss": "^8.4.7",
"postcss-loader": "^4.3.0",
"react": "^17.0.2", "react": "^17.0.2",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"sass-loader": "^10.2.1", "sass-loader": "^10.2.1",
@ -168,6 +171,8 @@
}, },
"pnpm": { "pnpm": {
"overrides": { "overrides": {
"@types/react": "^17.0.2",
"@types/react-dom": "^17.0.2",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2" "react-dom": "^17.0.2"
} }

View File

@ -3,7 +3,7 @@
exports[`AbbreviatedCard it renders correctly 1`] = ` exports[`AbbreviatedCard it renders correctly 1`] = `
<div> <div>
<div <div
class="components-surface components-card woocommerce-abbreviated-card css-1vyvcpq-View-Surface-getBorders-primary-Card-rounded em57xhy0" class="components-surface components-card woocommerce-abbreviated-card css-nsno0f-View-Surface-getBorders-primary-Card-rounded em57xhy0"
data-wp-c16t="true" data-wp-c16t="true"
data-wp-component="Card" data-wp-component="Card"
> >
@ -11,7 +11,7 @@ exports[`AbbreviatedCard it renders correctly 1`] = `
class="css-mgwsf4-View-Content em57xhy0" class="css-mgwsf4-View-Content em57xhy0"
> >
<div <div
class="components-card__body components-card-body css-1sfrl79-View-Body-borderRadius em57xhy0" class="components-card__body components-card-body css-1i4jx7i-View-Body-borderRadius em57xhy0"
data-wp-c16t="true" data-wp-c16t="true"
data-wp-component="CardBody" data-wp-component="CardBody"
> >

View File

@ -7,8 +7,7 @@ import { get, find, isArray } from 'lodash';
import interpolateComponents from '@automattic/interpolate-components'; import interpolateComponents from '@automattic/interpolate-components';
import classnames from 'classnames'; import classnames from 'classnames';
import { sprintf, __, _x } from '@wordpress/i18n'; import { sprintf, __, _x } from '@wordpress/i18n';
import { CurrencyFactory } from '@woocommerce/currency';
import CurrencyFactory from '@woocommerce/currency';
/** /**
* Internal dependencies * Internal dependencies

View File

@ -192,7 +192,7 @@ export const compareStrings = (
) => { ) => {
const string1 = s1.split( splitChar ); const string1 = s1.split( splitChar );
const string2 = s2.split( splitChar ); const string2 = s2.split( splitChar );
const diff = new Array(); const diff = [];
const long = s1.length > s2.length ? string1 : string2; const long = s1.length > s2.length ? string1 : string2;
for ( let x = 0; x < long.length; x++ ) { for ( let x = 0; x < long.length; x++ ) {
// eslint-disable-next-line no-unused-expressions // eslint-disable-next-line no-unused-expressions

View File

@ -131,7 +131,8 @@ export const DateTimePickerControl: React.FC< DateTimePickerControlProps > = ( {
const formatDateTimeForDisplay = useCallback( const formatDateTimeForDisplay = useCallback(
( dateTime: Moment ) => { ( dateTime: Moment ) => {
return dateTime.isValid() return dateTime.isValid()
? formatDate( displayFormat, dateTime.local() ) ? // @ts-expect-error TODO - fix this type error with moment
formatDate( displayFormat, dateTime.local() )
: dateTime.creationData().input?.toString() || ''; : dateTime.creationData().input?.toString() || '';
}, },
[ displayFormat ] [ displayFormat ]

View File

@ -8,7 +8,7 @@ import { createElement, useCallback, useState } from '@wordpress/element';
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { DateTimePickerControl, defaultDateFormat } from '../'; import { DateTimePickerControl } from '../';
export default { export default {
title: 'WooCommerce Admin/components/DateTimePickerControl', title: 'WooCommerce Admin/components/DateTimePickerControl',

View File

@ -103,6 +103,7 @@ describe( 'DateTimePickerControl', () => {
const input = container.querySelector( 'input' ); const input = container.querySelector( 'input' );
expect( input?.value ).toBe( expect( input?.value ).toBe(
// @ts-expect-error TODO - fix this type error with moment
formatDate( default24HourDateTimeFormat, dateTime ) formatDate( default24HourDateTimeFormat, dateTime )
); );
} ); } );
@ -122,6 +123,7 @@ describe( 'DateTimePickerControl', () => {
expect( input?.value ).toBe( expect( input?.value ).toBe(
formatDate( formatDate(
default24HourDateTimeFormat, default24HourDateTimeFormat,
// @ts-expect-error TODO - fix this type error with moment
moment.utc( ambiguousISODateTimeString ).local() moment.utc( ambiguousISODateTimeString ).local()
) )
); );
@ -142,6 +144,7 @@ describe( 'DateTimePickerControl', () => {
expect( input?.value ).toBe( expect( input?.value ).toBe(
formatDate( formatDate(
default24HourDateTimeFormat, default24HourDateTimeFormat,
// @ts-expect-error TODO - fix this type error with moment
moment.utc( unambiguousISODateTimeString ).local() moment.utc( unambiguousISODateTimeString ).local()
) )
); );
@ -159,6 +162,7 @@ describe( 'DateTimePickerControl', () => {
const input = container.querySelector( 'input' ); const input = container.querySelector( 'input' );
expect( input?.value ).toBe( expect( input?.value ).toBe(
// @ts-expect-error TODO - fix this type error with moment
formatDate( default12HourDateTimeFormat, dateTime ) formatDate( default12HourDateTimeFormat, dateTime )
); );
} ); } );
@ -175,6 +179,7 @@ describe( 'DateTimePickerControl', () => {
); );
const input = container.querySelector( 'input' ); const input = container.querySelector( 'input' );
// @ts-expect-error TODO - fix this type error with moment
expect( input?.value ).toBe( formatDate( dateTimeFormat, dateTime ) ); expect( input?.value ).toBe( formatDate( dateTimeFormat, dateTime ) );
} ); } );
@ -198,6 +203,7 @@ describe( 'DateTimePickerControl', () => {
const input = container.querySelector( 'input' ); const input = container.querySelector( 'input' );
expect( input?.value ).toBe( expect( input?.value ).toBe(
// @ts-expect-error TODO - fix this type error with moment
formatDate( default24HourDateTimeFormat, updatedDateTime ) formatDate( default24HourDateTimeFormat, updatedDateTime )
); );
} ); } );

View File

@ -23,9 +23,17 @@ export function useExpander( {
} }
}, [ item, shouldItemBeExpanded ] ); }, [ item, shouldItemBeExpanded ] );
function onExpand() {
setExpanded( true );
}
function onCollapse() {
setExpanded( false );
}
function onToggleExpand() { function onToggleExpand() {
setExpanded( ( prev ) => ! prev ); setExpanded( ( prev ) => ! prev );
} }
return { isExpanded, onToggleExpand }; return { isExpanded, onExpand, onCollapse, onToggleExpand };
} }

View File

@ -0,0 +1,31 @@
/**
* External dependencies
*/
import { useMemo } from 'react';
/**
* Internal dependencies
*/
import { CheckedStatus, TreeItemProps } from '../types';
export function useHighlighter( {
item,
multiple,
checkedStatus,
shouldItemBeHighlighted,
}: Pick< TreeItemProps, 'item' | 'multiple' | 'shouldItemBeHighlighted' > & {
checkedStatus: CheckedStatus;
} ) {
const isHighlighted = useMemo( () => {
if ( typeof shouldItemBeHighlighted === 'function' ) {
if ( multiple || item.children.length === 0 ) {
return shouldItemBeHighlighted( item );
}
}
if ( ! multiple ) {
return checkedStatus === 'checked';
}
}, [ item, multiple, checkedStatus, shouldItemBeHighlighted ] );
return { isHighlighted };
}

View File

@ -0,0 +1,173 @@
/**
* External dependencies
*/
/**
* Internal dependencies
*/
import { LinkedTree } from '../types';
function getFirstChild(
currentHeading: HTMLDivElement
): HTMLLabelElement | null {
const parentTreeItem = currentHeading?.closest< HTMLDivElement >(
'.experimental-woocommerce-tree-item'
);
const firstSubTreeItem = parentTreeItem?.querySelector(
'.experimental-woocommerce-tree > .experimental-woocommerce-tree-item'
);
const label = firstSubTreeItem?.querySelector< HTMLLabelElement >(
'.experimental-woocommerce-tree-item__heading > .experimental-woocommerce-tree-item__label'
);
return label ?? null;
}
function getFirstAncestor(
currentHeading: HTMLDivElement
): HTMLLabelElement | null {
const parentTree = currentHeading?.closest< HTMLDivElement >(
'.experimental-woocommerce-tree'
);
const grandParentTreeItem = parentTree?.closest< HTMLDivElement >(
'.experimental-woocommerce-tree-item'
);
const label = grandParentTreeItem?.querySelector< HTMLLabelElement >(
'.experimental-woocommerce-tree-item__heading > .experimental-woocommerce-tree-item__label'
);
return label ?? null;
}
function getAllHeadings(
currentHeading: HTMLDivElement
): NodeListOf< HTMLDivElement > | undefined {
const rootTree = currentHeading.closest< HTMLDivElement >(
'.experimental-woocommerce-tree--level-1'
);
return rootTree?.querySelectorAll< HTMLDivElement >(
'.experimental-woocommerce-tree-item > .experimental-woocommerce-tree-item__heading'
);
}
const step = {
ArrowDown: 1,
ArrowUp: -1,
};
function getNextFocusableElement(
currentHeading: HTMLDivElement,
code: 'ArrowDown' | 'ArrowUp'
): HTMLLabelElement | null {
const headingsNodeList = getAllHeadings( currentHeading );
if ( ! headingsNodeList ) return null;
let currentHeadingIndex = 0;
for ( const heading of headingsNodeList.values() ) {
if ( heading === currentHeading ) break;
currentHeadingIndex++;
}
if (
currentHeadingIndex < 0 ||
currentHeadingIndex >= headingsNodeList.length
) {
return null;
}
const heading = headingsNodeList.item(
currentHeadingIndex + ( step[ code ] ?? 0 )
);
return heading?.querySelector< HTMLLabelElement >(
'.experimental-woocommerce-tree-item__label'
);
}
function getFirstFocusableElement(
currentHeading: HTMLDivElement
): HTMLLabelElement | null {
const headingsNodeList = getAllHeadings( currentHeading );
if ( ! headingsNodeList ) return null;
return headingsNodeList
.item( 0 )
.querySelector< HTMLLabelElement >(
'.experimental-woocommerce-tree-item__label'
);
}
function getLastFocusableElement(
currentHeading: HTMLDivElement
): HTMLLabelElement | null {
const headingsNodeList = getAllHeadings( currentHeading );
if ( ! headingsNodeList ) return null;
return headingsNodeList
.item( headingsNodeList.length - 1 )
.querySelector< HTMLLabelElement >(
'.experimental-woocommerce-tree-item__label'
);
}
export function useKeyboard( {
item,
isExpanded,
onExpand,
onCollapse,
onToggleExpand,
}: {
item: LinkedTree;
isExpanded: boolean;
onExpand(): void;
onCollapse(): void;
onToggleExpand(): void;
} ) {
function onKeyDown( event: React.KeyboardEvent< HTMLDivElement > ) {
if ( event.code === 'ArrowRight' ) {
event.preventDefault();
if ( item.children.length > 0 ) {
if ( isExpanded ) {
const element = getFirstChild( event.currentTarget );
return element?.focus();
}
onExpand();
}
}
if ( event.code === 'ArrowLeft' ) {
event.preventDefault();
if ( ! isExpanded && item.parent ) {
const element = getFirstAncestor( event.currentTarget );
return element?.focus();
}
if ( item.children.length > 0 ) {
onCollapse();
}
}
if ( event.code === 'Enter' ) {
event.preventDefault();
if ( item.children.length > 0 ) {
onToggleExpand();
}
}
if ( event.code === 'ArrowDown' || event.code === 'ArrowUp' ) {
event.preventDefault();
const element = getNextFocusableElement(
event.currentTarget,
event.code
);
element?.focus();
}
if ( event.code === 'Home' ) {
event.preventDefault();
const element = getFirstFocusableElement( event.currentTarget );
element?.focus();
}
if ( event.code === 'End' ) {
event.preventDefault();
const element = getLastFocusableElement( event.currentTarget );
element?.focus();
}
}
return { onKeyDown };
}

View File

@ -0,0 +1,167 @@
/**
* External dependencies
*/
import { useMemo } from 'react';
/**
* Internal dependencies
*/
import { CheckedStatus, Item, LinkedTree, TreeItemProps } from '../types';
let selectedItemsMap: Record< string, number > = {};
let indeterminateMemo: Record< string, boolean > = {};
function getDeepChildren( item: LinkedTree ) {
if ( item.children.length ) {
const children = item.children.map( ( { data } ) => data );
item.children.forEach( ( child ) => {
children.push( ...getDeepChildren( child ) );
} );
return children;
}
return [];
}
function isIndeterminate(
selectedItems: Record< string, number >,
children?: LinkedTree[],
memo: Record< string, boolean > = indeterminateMemo
): boolean {
if ( children?.length ) {
for ( const child of children ) {
if ( child.data.value in indeterminateMemo ) {
return true;
}
const isChildSelected = child.data.value in selectedItems;
if (
! isChildSelected ||
isIndeterminate( selectedItems, child.children, memo )
) {
indeterminateMemo[ child.data.value ] = true;
return true;
}
}
}
return false;
}
function mapSelectedItems(
selected: Item | Item[] = []
): Record< string, number > {
const selectedArray = Array.isArray( selected ) ? selected : [ selected ];
return selectedArray.reduce(
( map, selectedItem, index ) => ( {
...map,
[ selectedItem.value ]: index,
} ),
{} as Record< string, number >
);
}
function hasSelectedSibblingChildren(
children: LinkedTree[],
values: Item[],
selectedItems: Record< string, number >
) {
return children.some( ( child ) => {
const isChildSelected = child.data.value in selectedItems;
if ( ! isChildSelected ) return false;
return ! values.some(
( childValue ) => childValue.value === child.data.value
);
} );
}
export function useSelection( {
item,
multiple,
selected,
level,
index,
onSelect,
onRemove,
}: Pick<
TreeItemProps,
| 'item'
| 'multiple'
| 'selected'
| 'level'
| 'index'
| 'onSelect'
| 'onRemove'
> ) {
const selectedItems = useMemo( () => {
if ( level === 1 && index === 0 ) {
selectedItemsMap = mapSelectedItems( selected );
indeterminateMemo = {} as Record< string, boolean >;
}
return selectedItemsMap;
}, [ selected, level, index ] );
const checkedStatus: CheckedStatus = useMemo( () => {
if ( item.data.value in selectedItems ) {
if ( multiple && isIndeterminate( selectedItems, item.children ) ) {
return 'indeterminate';
}
return 'checked';
}
return 'unchecked';
}, [ selectedItems, item, multiple ] );
function onSelectChild( checked: boolean ) {
let value: Item | Item[] = item.data;
if ( multiple ) {
value = [ item.data ];
if ( item.children.length ) {
value.push( ...getDeepChildren( item ) );
}
} else if ( item.children?.length ) {
return;
}
if ( checked ) {
if ( typeof onSelect === 'function' ) {
onSelect( value );
}
} else if ( typeof onRemove === 'function' ) {
onRemove( value );
}
}
function onSelectChildren( value: Item | Item[] ) {
if ( typeof onSelect !== 'function' ) return;
if ( multiple ) {
value = [ item.data, ...( value as Item[] ) ];
}
onSelect( value );
}
function onRemoveChildren( value: Item | Item[] ) {
if ( typeof onRemove !== 'function' ) return;
if ( multiple && item.children?.length ) {
const hasSelectedSibbling = hasSelectedSibblingChildren(
item.children,
value as Item[],
selectedItems
);
if ( ! hasSelectedSibbling ) {
value = [ item.data, ...( value as Item[] ) ];
}
}
onRemove( value );
}
return {
multiple,
selected,
checkedStatus,
onSelectChild,
onSelectChildren,
onRemoveChildren,
};
}

View File

@ -2,17 +2,28 @@
* External dependencies * External dependencies
*/ */
import React from 'react'; import React from 'react';
import { useInstanceId } from '@wordpress/compose';
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { TreeItemProps } from '../types'; import { TreeItemProps } from '../types';
import { useExpander } from './use-expander'; import { useExpander } from './use-expander';
import { useHighlighter } from './use-highlighter';
import { useKeyboard } from './use-keyboard';
import { useSelection } from './use-selection';
export function useTreeItem( { export function useTreeItem( {
item, item,
level, level,
multiple,
selected,
index,
getLabel,
shouldItemBeExpanded, shouldItemBeExpanded,
shouldItemBeHighlighted,
onSelect,
onRemove,
...props ...props
}: TreeItemProps ) { }: TreeItemProps ) {
const nextLevel = level + 1; const nextLevel = level + 1;
@ -22,22 +33,71 @@ export function useTreeItem( {
shouldItemBeExpanded, shouldItemBeExpanded,
} ); } );
const selection = useSelection( {
item,
multiple,
selected,
level,
index,
onSelect,
onRemove,
} );
const highlighter = useHighlighter( {
item,
checkedStatus: selection.checkedStatus,
multiple,
shouldItemBeHighlighted,
} );
const subTreeId = `experimental-woocommerce-tree__group-${ useInstanceId(
useTreeItem
) }`;
const { onKeyDown } = useKeyboard( {
...expander,
item,
} );
return { return {
item, item,
level: nextLevel, level: nextLevel,
expander, expander,
selection,
highlighter,
getLabel,
treeItemProps: { treeItemProps: {
...props, ...props,
role: 'none',
}, },
headingProps: { headingProps: {
role: 'treeitem',
'aria-selected': selection.checkedStatus !== 'unchecked',
'aria-expanded': item.children.length
? expander.isExpanded
: undefined,
'aria-owns':
item.children.length && expander.isExpanded
? subTreeId
: undefined,
style: { style: {
'--level': level, '--level': level,
} as React.CSSProperties, } as React.CSSProperties,
onKeyDown,
}, },
treeProps: { treeProps: {
id: subTreeId,
items: item.children, items: item.children,
level: nextLevel, level: nextLevel,
multiple: selection.multiple,
selected: selection.selected,
role: 'group',
'aria-label': item.data.label,
getItemLabel: getLabel,
shouldItemBeExpanded, shouldItemBeExpanded,
shouldItemBeHighlighted,
onSelect: selection.onSelectChildren,
onRemove: selection.onRemoveChildren,
}, },
}; };
} }

View File

@ -11,7 +11,14 @@ export function useTree( {
ref, ref,
items, items,
level = 1, level = 1,
role = 'tree',
multiple,
selected,
getItemLabel,
shouldItemBeExpanded, shouldItemBeExpanded,
shouldItemBeHighlighted,
onSelect,
onRemove,
...props ...props
}: TreeProps ) { }: TreeProps ) {
return { return {
@ -19,10 +26,17 @@ export function useTree( {
items, items,
treeProps: { treeProps: {
...props, ...props,
role,
}, },
treeItemProps: { treeItemProps: {
level, level,
multiple,
selected,
getLabel: getItemLabel,
shouldItemBeExpanded, shouldItemBeExpanded,
shouldItemBeHighlighted,
onSelect,
onRemove,
}, },
}; };
} }

View File

@ -1,14 +1,16 @@
/** /**
* External dependencies * External dependencies
*/ */
import interpolate from '@automattic/interpolate-components';
import { BaseControl, TextControl } from '@wordpress/components'; import { BaseControl, TextControl } from '@wordpress/components';
import React, { createElement, useCallback, useState } from 'react'; import React, { createElement, useCallback, useRef, useState } from 'react';
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { TreeControl } from '../tree-control'; import { TreeControl } from '../tree-control';
import { Item, LinkedTree } from '../types'; import { Item, LinkedTree } from '../types';
import '../tree.scss';
const listItems: Item[] = [ const listItems: Item[] = [
{ value: '1', label: 'Technology' }, { value: '1', label: 'Technology' },
@ -65,6 +67,193 @@ export const ExpandOnFilter: React.FC = () => {
); );
}; };
export const CustomItemLabel: React.FC = () => {
function renderCustomItemLabel( item: LinkedTree ) {
return (
<div style={ { display: 'flex', gap: 8 } }>
<div
style={ {
width: 36,
height: 36,
backgroundColor: '#ccc',
borderRadius: 2,
} }
/>
<div
style={ {
display: 'flex',
flexDirection: 'column',
} }
>
<strong>{ item.data.label }</strong>
<small>Some item description</small>
</div>
</div>
);
}
return (
<BaseControl label="Custom item label" id="custom-item-label">
<TreeControl
id="custom-item-label"
items={ listItems }
getItemLabel={ renderCustomItemLabel }
/>
</BaseControl>
);
};
function getItemLabel( item: LinkedTree, text: string ) {
return (
<span>
{ text
? interpolate( {
mixedString: item.data.label.replace(
new RegExp( text, 'ig' ),
( group ) => `{{bold}}${ group }{{/bold}}`
),
components: {
bold: <b />,
},
} )
: item.data.label }
</span>
);
}
export const CustomItemLabelOnSearch: React.FC = () => {
const [ text, setText ] = useState( '' );
return (
<>
<TextControl value={ text } onChange={ setText } />
<BaseControl
label="Custom item label on search"
id="custom-item-label-on-search"
>
<TreeControl
id="custom-item-label-on-search"
items={ listItems }
getItemLabel={ ( item ) => getItemLabel( item, text ) }
shouldItemBeExpanded={ useCallback(
( item ) => shouldItemBeExpanded( item, text ),
[ text ]
) }
/>
</BaseControl>
</>
);
};
export const SelectionSingle: React.FC = () => {
const [ selected, setSelected ] = useState( listItems[ 1 ] );
return (
<>
<BaseControl label="Single selection" id="single-selection">
<TreeControl
id="single-selection"
items={ listItems }
selected={ selected }
onSelect={ ( value: Item ) => setSelected( value ) }
/>
</BaseControl>
<pre>{ JSON.stringify( selected, null, 2 ) }</pre>
</>
);
};
export const SelectionMultiple: React.FC = () => {
const [ selected, setSelected ] = useState( [
listItems[ 0 ],
listItems[ 1 ],
] );
function handleSelect( values: Item[] ) {
setSelected( ( items ) => {
const newItems = values.filter(
( { value } ) =>
! items.some( ( item ) => item.value === value )
);
return [ ...items, ...newItems ];
} );
}
function handleRemove( values: Item[] ) {
setSelected( ( items ) =>
items.filter(
( item ) =>
! values.some( ( { value } ) => item.value === value )
)
);
}
return (
<>
<BaseControl label="Multiple selection" id="multiple-selection">
<TreeControl
id="multiple-selection"
items={ listItems }
multiple
selected={ selected }
onSelect={ handleSelect }
onRemove={ handleRemove }
/>
</BaseControl>
<pre>{ JSON.stringify( selected, null, 2 ) }</pre>
</>
);
};
function getFirstMatchingItem(
item: LinkedTree,
text: string,
memo: Record< string, string >
) {
if ( ! text ) return false;
if ( memo[ text ] === item.data.value ) return true;
const matcher = new RegExp( text, 'ig' );
if ( matcher.test( item.data.label ) ) {
if ( ! memo[ text ] ) {
memo[ text ] = item.data.value;
return true;
}
}
return false;
}
export const HighlightFirstMatchingItem: React.FC = () => {
const [ text, setText ] = useState( '' );
const memo = useRef< Record< string, string > >( {} );
return (
<>
<TextControl value={ text } onChange={ setText } />
<BaseControl
label="Highlight first matching item"
id="highlight-first-matching-item"
>
<TreeControl
id="highlight-first-matching-item"
items={ listItems }
getItemLabel={ ( item ) => getItemLabel( item, text ) }
shouldItemBeExpanded={ useCallback(
( item ) => shouldItemBeExpanded( item, text ),
[ text ]
) }
shouldItemBeHighlighted={ ( item ) =>
getFirstMatchingItem( item, text, memo.current )
}
/>
</BaseControl>
</>
);
};
export default { export default {
title: 'WooCommerce Admin/experimental/TreeControl', title: 'WooCommerce Admin/experimental/TreeControl',
component: TreeControl, component: TreeControl,

View File

@ -1,12 +1,21 @@
$control-size: $gap-large;
.experimental-woocommerce-tree-item { .experimental-woocommerce-tree-item {
margin: 0; margin: 0;
&--highlighted {
> .experimental-woocommerce-tree-item__heading {
background-color: $gray-100;
}
}
&__heading { &__heading {
display: flex; display: flex;
flex-grow: 1; flex-grow: 1;
gap: $gap-smaller; gap: $gap-smaller;
min-height: $gap-largest; min-height: $gap-largest;
padding: 0 $gap-small 0 calc( ( var( --level ) - 1 ) * ( $gap + $gap-small ) + $gap-small ); padding: 0 $gap-small 0
calc( ( var( --level ) - 1 ) * ( $gap + $gap-small ) + $gap-small );
border-radius: 2px; border-radius: 2px;
&:hover, &:hover,
@ -17,9 +26,10 @@
&:hover, &:hover,
&:focus-within { &:focus-within {
background-color: $gray-0; background-color: $gray-100;
} }
} }
&__label { &__label {
display: flex; display: flex;
flex-grow: 1; flex-grow: 1;
@ -30,13 +40,50 @@
> span { > span {
display: block; display: block;
} }
.components-base-control__field {
margin: 0;
} }
.components-radio-control__input {
@include screen-reader-only();
}
.components-checkbox-control__label {
display: none;
}
.components-checkbox-control__input-container {
display: block;
width: $control-size;
height: $control-size;
}
svg.components-checkbox-control__checked,
svg.components-checkbox-control__indeterminate,
.components-checkbox-control__input[type='checkbox'] {
position: absolute;
border-color: $gray-700;
width: $control-size;
height: $control-size;
top: 0;
left: 0;
&:focus {
outline: none;
box-shadow: none;
}
}
}
&__expander { &__expander {
display: flex; display: flex;
align-items: center; align-items: center;
.components-button { .components-button {
padding: 0; padding: 0;
height: $control-size;
width: $control-size;
min-width: $control-size;
} }
} }
} }

View File

@ -1,7 +1,7 @@
/** /**
* External dependencies * External dependencies
*/ */
import { Button } from '@wordpress/components'; import { Button, CheckboxControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n'; import { __ } from '@wordpress/i18n';
import { chevronDown, chevronUp } from '@wordpress/icons'; import { chevronDown, chevronUp } from '@wordpress/icons';
import classNames from 'classnames'; import classNames from 'classnames';
@ -24,6 +24,9 @@ export const TreeItem = forwardRef( function ForwardedTreeItem(
headingProps, headingProps,
treeProps, treeProps,
expander: { isExpanded, onToggleExpand }, expander: { isExpanded, onToggleExpand },
selection,
highlighter: { isHighlighted },
getLabel,
} = useTreeItem( { } = useTreeItem( {
...props, ...props,
ref, ref,
@ -34,16 +37,46 @@ export const TreeItem = forwardRef( function ForwardedTreeItem(
{ ...treeItemProps } { ...treeItemProps }
className={ classNames( className={ classNames(
treeItemProps.className, treeItemProps.className,
'experimental-woocommerce-tree-item' 'experimental-woocommerce-tree-item',
{
'experimental-woocommerce-tree-item--highlighted':
isHighlighted,
}
) } ) }
> >
<div <div
{ ...headingProps } { ...headingProps }
className="experimental-woocommerce-tree-item__heading" className="experimental-woocommerce-tree-item__heading"
> >
<div className="experimental-woocommerce-tree-item__label"> { /* eslint-disable-next-line jsx-a11y/label-has-for */ }
<label className="experimental-woocommerce-tree-item__label">
{ selection.multiple ? (
<CheckboxControl
indeterminate={
selection.checkedStatus === 'indeterminate'
}
checked={ selection.checkedStatus === 'checked' }
onChange={ selection.onSelectChild }
/>
) : (
<input
type="radio"
checked={ selection.checkedStatus === 'checked' }
className="components-radio-control__input"
onChange={ ( event ) =>
selection.onSelectChild(
event.currentTarget.checked
)
}
/>
) }
{ typeof getLabel === 'function' ? (
getLabel( item )
) : (
<span>{ item.data.label }</span> <span>{ item.data.label }</span>
</div> ) }
</label>
{ Boolean( item.children?.length ) && ( { Boolean( item.children?.length ) && (
<div className="experimental-woocommerce-tree-item__expander"> <div className="experimental-woocommerce-tree-item__expander">

View File

@ -30,11 +30,12 @@ export const Tree = forwardRef( function ForwardedTree(
`experimental-woocommerce-tree--level-${ level }` `experimental-woocommerce-tree--level-${ level }`
) } ) }
> >
{ items.map( ( child ) => ( { items.map( ( child, index ) => (
<TreeItem <TreeItem
{ ...treeItemProps } { ...treeItemProps }
key={ child.data.value } key={ child.data.value }
item={ child } item={ child }
index={ index }
/> />
) ) } ) ) }
</ol> </ol>

View File

@ -10,12 +10,75 @@ export interface LinkedTree {
children: LinkedTree[]; children: LinkedTree[];
} }
export type TreeProps = React.DetailedHTMLProps< export type CheckedStatus = 'checked' | 'unchecked' | 'indeterminate';
type BaseTreeProps = {
/**
* It contians one item if `multiple` value is false or
* a list of items if it is true.
*/
selected?: Item | Item[];
/**
* Whether the tree items are single or multiple selected.
*/
multiple?: boolean;
/**
* When `multiple` is true and a child item is selected, all its
* ancestors and its descendants are also selected. If it's false
* only the clicked item is selected.
*
* @param value The selection
*/
onSelect?( value: Item | Item[] ): void;
/**
* When `multiple` is true and a child item is unselected, all its
* ancestors (if no sibblings are selected) and its descendants
* are also unselected. If it's false only the clicked item is
* unselected.
*
* @param value The unselection
*/
onRemove?( value: Item | Item[] ): void;
/**
* It provides a way to determine whether the current rendering
* item is highlighted or not from outside the tree.
*
* @example
* <Tree
* shouldItemBeHighlighted={ isFirstChild }
* />
*
* @param item The current linked tree item, useful to
* traverse the entire linked tree from this item.
*
* @see {@link LinkedTree}
*/
shouldItemBeHighlighted?( item: LinkedTree ): boolean;
};
export type TreeProps = BaseTreeProps &
Omit<
React.DetailedHTMLProps<
React.OlHTMLAttributes< HTMLOListElement >, React.OlHTMLAttributes< HTMLOListElement >,
HTMLOListElement HTMLOListElement
> & { >,
'onSelect'
> & {
level?: number; level?: number;
items: LinkedTree[]; items: LinkedTree[];
/** It gives a way to render a different Element as the
* tree item label.
*
* @example
* <Tree
* getItemLabel={ ( item ) => <span>${ item.data.label }</span> }
* />
*
* @param item The current rendering tree item
*
* @see {@link LinkedTree}
*/
getItemLabel?( item: LinkedTree ): JSX.Element;
/** /**
* Return if the tree item passed in should be expanded. * Return if the tree item passed in should be expanded.
* *
@ -31,16 +94,22 @@ export type TreeProps = React.DetailedHTMLProps<
* @see {@link LinkedTree} * @see {@link LinkedTree}
*/ */
shouldItemBeExpanded?( item: LinkedTree ): boolean; shouldItemBeExpanded?( item: LinkedTree ): boolean;
}; };
export type TreeItemProps = React.DetailedHTMLProps< export type TreeItemProps = BaseTreeProps &
Omit<
React.DetailedHTMLProps<
React.LiHTMLAttributes< HTMLLIElement >, React.LiHTMLAttributes< HTMLLIElement >,
HTMLLIElement HTMLLIElement
> & { >,
'onSelect'
> & {
level: number; level: number;
item: LinkedTree; item: LinkedTree;
index: number;
getLabel?( item: LinkedTree ): JSX.Element;
shouldItemBeExpanded?( item: LinkedTree ): boolean; shouldItemBeExpanded?( item: LinkedTree ): boolean;
}; };
export type TreeControlProps = Omit< TreeProps, 'items' | 'level' > & { export type TreeControlProps = Omit< TreeProps, 'items' | 'level' > & {
items: Item[]; items: Item[];

View File

@ -0,0 +1,9 @@
import * as components from '@wordpress/components';
declare module '@wordpress/components' {
declare namespace CheckboxControl {
interface Props {
indeterminate?: boolean;
}
}
}

View File

@ -7,7 +7,7 @@ import { find } from 'lodash';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { updateQueryString } from '@woocommerce/navigation'; import { updateQueryString } from '@woocommerce/navigation';
import { getDateParamsFromQuery, getCurrentDates } from '@woocommerce/date'; import { getDateParamsFromQuery, getCurrentDates } from '@woocommerce/date';
import CurrencyFactory from '@woocommerce/currency'; import { CurrencyFactory } from '@woocommerce/currency';
/** /**
* Internal dependencies * Internal dependencies

View File

@ -95,3 +95,11 @@ export {
SlotContextType, SlotContextType,
SlotContextHelpersType, SlotContextHelpersType,
} from './slot-context'; } from './slot-context';
export { TreeControl as __experimentalTreeControl } from './experimental-tree-control';
export { default as TreeSelectControl } from './tree-select-control';
// Exports below can be removed once the @woocommerce/product-editor package is released.
export {
ProductSectionLayout as __experimentalProductSectionLayout,
ProductFieldSection as __experimentalProductFieldSection,
} from './product-section-layout';

View File

@ -12,11 +12,6 @@ import { useState } from '@wordpress/element';
import { MediaUploader } from '../'; import { MediaUploader } from '../';
import { File } from '../types'; import { File } from '../types';
declare let Blob: {
prototype: Blob;
new (): Blob;
};
const MockMediaUpload = ( { onSelect, render } ) => { const MockMediaUpload = ( { onSelect, render } ) => {
const [ isOpen, setOpen ] = useState( false ); const [ isOpen, setOpen ] = useState( false );

View File

@ -9,7 +9,7 @@ import {
useState, useState,
useEffect, useEffect,
} from '@wordpress/element'; } from '@wordpress/element';
import { SyntheticEvent } from 'react'; import { SyntheticEvent, useCallback } from 'react';
import { useDispatch, useSelect } from '@wordpress/data'; import { useDispatch, useSelect } from '@wordpress/data';
import { PLUGINS_STORE_NAME, InstallPluginsResponse } from '@woocommerce/data'; import { PLUGINS_STORE_NAME, InstallPluginsResponse } from '@woocommerce/data';
@ -25,6 +25,11 @@ type PluginsProps = {
pluginSlugs?: string[]; pluginSlugs?: string[];
onAbort?: () => void; onAbort?: () => void;
abortText?: string; abortText?: string;
installText?: string;
installButtonVariant?: Button.BaseProps[ 'variant' ];
learnMoreLink?: string;
learnMoreText?: string;
onLearnMore?: () => void;
}; };
export const Plugins = ( { export const Plugins = ( {
@ -34,10 +39,17 @@ export const Plugins = ( {
onError = () => null, onError = () => null,
pluginSlugs = [ 'jetpack', 'woocommerce-services' ], pluginSlugs = [ 'jetpack', 'woocommerce-services' ],
onSkip, onSkip,
installText = __( 'Install & enable', 'woocommerce' ),
skipText = __( 'No thanks', 'woocommerce' ), skipText = __( 'No thanks', 'woocommerce' ),
abortText = __( 'Abort', 'woocommerce' ), abortText = __( 'Abort', 'woocommerce' ),
installButtonVariant = 'primary',
learnMoreLink,
learnMoreText = __( 'Learn more', 'woocommerce' ),
onLearnMore,
}: PluginsProps ) => { }: PluginsProps ) => {
const [ hasErrors, setHasErrors ] = useState( false ); const [ hasErrors, setHasErrors ] = useState( false );
// Tracks action so that multiple instances of this button don't all light up when one is clicked
const [ hasBeenClicked, setHasBeenClicked ] = useState( false );
const { installAndActivatePlugins } = useDispatch( PLUGINS_STORE_NAME ); const { installAndActivatePlugins } = useDispatch( PLUGINS_STORE_NAME );
const { isRequesting } = useSelect( ( select ) => { const { isRequesting } = useSelect( ( select ) => {
const { getActivePlugins, getInstalledPlugins, isPluginsRequesting } = const { getActivePlugins, getInstalledPlugins, isPluginsRequesting } =
@ -52,25 +64,24 @@ export const Plugins = ( {
}; };
} ); } );
const handleErrors = ( const handleErrors = useCallback(
errors: unknown, ( errors: unknown, response: InstallPluginsResponse ) => {
response: InstallPluginsResponse
) => {
setHasErrors( true ); setHasErrors( true );
onError( errors, response ); onError( errors, response );
}; },
[ onError ]
);
const handleSuccess = ( const handleSuccess = useCallback(
plugins: string[], ( plugins: string[], response: InstallPluginsResponse ) => {
response: InstallPluginsResponse
) => {
onComplete( plugins, response ); onComplete( plugins, response );
}; },
[ onComplete ]
);
const installAndActivate = async ( const installAndActivate = useCallback(
event?: SyntheticEvent< HTMLAnchorElement > async ( event?: SyntheticEvent< HTMLAnchorElement > ) => {
) => {
if ( event ) { if ( event ) {
event.preventDefault(); event.preventDefault();
} }
@ -85,15 +96,24 @@ export const Plugins = ( {
handleSuccess( response.data.activated, response ); handleSuccess( response.data.activated, response );
} ) } )
.catch( ( response ) => { .catch( ( response ) => {
setHasBeenClicked( false );
handleErrors( response.errors, response ); handleErrors( response.errors, response );
} ); } );
}; },
[
handleErrors,
handleSuccess,
installAndActivatePlugins,
isRequesting,
pluginSlugs,
]
);
useEffect( () => { useEffect( () => {
if ( autoInstall ) { if ( autoInstall ) {
installAndActivate(); installAndActivate();
} }
}, [] ); }, [ autoInstall, installAndActivate ] );
if ( hasErrors ) { if ( hasErrors ) {
return ( return (
@ -131,17 +151,32 @@ export const Plugins = ( {
return ( return (
<> <>
<Button <Button
isBusy={ isRequesting } isBusy={ isRequesting && hasBeenClicked }
isPrimary variant={
onClick={ installAndActivate } isRequesting && hasBeenClicked
? 'primary' // set to primary when busy, the other variants look weird when combined with isBusy
: installButtonVariant
}
disabled={ isRequesting && hasBeenClicked }
onClick={ () => {
setHasBeenClicked( true );
installAndActivate();
} }
> >
{ __( 'Install & enable', 'woocommerce' ) } { installText }
</Button> </Button>
{ onSkip && ( { onSkip && (
<Button isTertiary onClick={ onSkip }> <Button isTertiary onClick={ onSkip }>
{ skipText } { skipText }
</Button> </Button>
) } ) }
{ learnMoreLink && (
<a href={ learnMoreLink } target="_blank" rel="noreferrer">
<Button isTertiary onClick={ onLearnMore }>
{ learnMoreText }
</Button>
</a>
) }
{ onAbort && ( { onAbort && (
<Button isTertiary onClick={ onAbort }> <Button isTertiary onClick={ onAbort }>
{ abortText } { abortText }

View File

@ -2,7 +2,7 @@
* External dependencies * External dependencies
*/ */
import { createElement } from '@wordpress/element'; import { createElement } from '@wordpress/element';
import { RadioControl, SelectControl } from '@wordpress/components'; import { SelectControl } from '@wordpress/components';
/** /**
* Internal dependencies * Internal dependencies

View File

@ -0,0 +1,47 @@
/**
* External dependencies
*/
import { createElement } from '@wordpress/element';
import { Card, CardBody } from '@wordpress/components';
import deprecated from '@wordpress/deprecated';
/**
* Internal dependencies
*/
import { ProductSectionLayout } from './product-section-layout';
import { WooProductFieldItem } from '../woo-product-field-item';
type ProductFieldSectionProps = {
id: string;
title: string;
description: string | JSX.Element;
className?: string;
};
export const ProductFieldSection: React.FC< ProductFieldSectionProps > = ( {
id,
title,
description,
className,
children,
} ) => {
deprecated( `__experimentalProductFieldSection`, {
version: '13.0.0',
plugin: '@woocommerce/components',
hint: 'Moved to @woocommerce/product-editor package: import { __experimentalProductFieldSection } from @woocommerce/product-editor',
} );
return (
<ProductSectionLayout
title={ title }
description={ description }
className={ className }
>
<Card>
<CardBody>
{ children }
<WooProductFieldItem.Slot section={ id } />
</CardBody>
</Card>
</ProductSectionLayout>
);
};

View File

@ -0,0 +1,44 @@
/**
* External dependencies
*/
import { Children, isValidElement, createElement } from '@wordpress/element';
import deprecated from '@wordpress/deprecated';
/**
* Internal dependencies
*/
import { FormSection } from '../form-section';
type ProductSectionLayoutProps = {
title: string;
description: string | JSX.Element;
className?: string;
};
export const ProductSectionLayout: React.FC< ProductSectionLayoutProps > = ( {
title,
description,
className,
children,
} ) => {
deprecated( `__experimentalProductSectionLayout`, {
version: '13.0.0',
plugin: '@woocommerce/components',
hint: 'Moved to @woocommerce/product-editor package: import { __experimentalProductSectionLayout } from @woocommerce/product-editor',
} );
return (
<FormSection
title={ title }
description={ description }
className={ className }
>
{ Children.map( children, ( child ) => {
if ( isValidElement( child ) && child.props.onChange ) {
return (
<div className="product-field-layout">{ child }</div>
);
}
return child;
} ) }
</FormSection>
);
};

View File

@ -82,20 +82,6 @@ export const SearchListControl = ( props ) => {
}; };
}; };
const onSelect = ( item ) => {
return () => {
if ( isSelected( item ) ) {
onRemove( item.id )();
return;
}
if ( isSingle ) {
onChange( [ item ] );
} else {
onChange( [ ...selected, item ] );
}
};
};
const isSelected = ( item ) => const isSelected = ( item ) =>
findIndex( selected, { id: item.id } ) !== -1; findIndex( selected, { id: item.id } ) !== -1;
@ -114,6 +100,20 @@ export const SearchListControl = ( props ) => {
: filteredList; : filteredList;
}; };
const onSelect = ( item ) => {
return () => {
if ( isSelected( item ) ) {
onRemove( item.id )();
return;
}
if ( isSingle ) {
onChange( [ item ] );
} else {
onChange( [ ...selected, item ] );
}
};
};
const defaultRenderItem = ( args ) => { const defaultRenderItem = ( args ) => {
return <SearchListItem { ...args } />; return <SearchListItem { ...args } />;
}; };

View File

@ -47,13 +47,13 @@ Object {
class="woocommerce-search-list__search" class="woocommerce-search-list__search"
> >
<div <div
class="components-base-control css-wdf2ti-Wrapper e1puf3u4" class="components-base-control css-wdf2ti-Wrapper ej5x27r4"
> >
<div <div
class="components-base-control__field css-igk9ll-StyledField e1puf3u3" class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
> >
<label <label
class="components-base-control__label css-eweeby-StyledLabel-labelStyles e1puf3u2" class="components-base-control__label css-eweeby-StyledLabel-labelStyles ej5x27r2"
for="inspector-text-control-10" for="inspector-text-control-10"
> >
Search for items Search for items
@ -246,13 +246,13 @@ Object {
class="woocommerce-search-list__search" class="woocommerce-search-list__search"
> >
<div <div
class="components-base-control css-wdf2ti-Wrapper e1puf3u4" class="components-base-control css-wdf2ti-Wrapper ej5x27r4"
> >
<div <div
class="components-base-control__field css-igk9ll-StyledField e1puf3u3" class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
> >
<label <label
class="components-base-control__label css-eweeby-StyledLabel-labelStyles e1puf3u2" class="components-base-control__label css-eweeby-StyledLabel-labelStyles ej5x27r2"
for="inspector-text-control-10" for="inspector-text-control-10"
> >
Search for items Search for items
@ -526,13 +526,13 @@ Object {
class="woocommerce-search-list__search" class="woocommerce-search-list__search"
> >
<div <div
class="components-base-control css-wdf2ti-Wrapper e1puf3u4" class="components-base-control css-wdf2ti-Wrapper ej5x27r4"
> >
<div <div
class="components-base-control__field css-igk9ll-StyledField e1puf3u3" class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
> >
<label <label
class="components-base-control__label css-eweeby-StyledLabel-labelStyles e1puf3u2" class="components-base-control__label css-eweeby-StyledLabel-labelStyles ej5x27r2"
for="inspector-text-control-0" for="inspector-text-control-0"
> >
Search for items Search for items
@ -710,13 +710,13 @@ Object {
class="woocommerce-search-list__search" class="woocommerce-search-list__search"
> >
<div <div
class="components-base-control css-wdf2ti-Wrapper e1puf3u4" class="components-base-control css-wdf2ti-Wrapper ej5x27r4"
> >
<div <div
class="components-base-control__field css-igk9ll-StyledField e1puf3u3" class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
> >
<label <label
class="components-base-control__label css-eweeby-StyledLabel-labelStyles e1puf3u2" class="components-base-control__label css-eweeby-StyledLabel-labelStyles ej5x27r2"
for="inspector-text-control-0" for="inspector-text-control-0"
> >
Search for items Search for items
@ -975,13 +975,13 @@ Object {
class="woocommerce-search-list__search" class="woocommerce-search-list__search"
> >
<div <div
class="components-base-control css-wdf2ti-Wrapper e1puf3u4" class="components-base-control css-wdf2ti-Wrapper ej5x27r4"
> >
<div <div
class="components-base-control__field css-igk9ll-StyledField e1puf3u3" class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
> >
<label <label
class="components-base-control__label css-eweeby-StyledLabel-labelStyles e1puf3u2" class="components-base-control__label css-eweeby-StyledLabel-labelStyles ej5x27r2"
for="inspector-text-control-1" for="inspector-text-control-1"
> >
Search for items Search for items
@ -1159,13 +1159,13 @@ Object {
class="woocommerce-search-list__search" class="woocommerce-search-list__search"
> >
<div <div
class="components-base-control css-wdf2ti-Wrapper e1puf3u4" class="components-base-control css-wdf2ti-Wrapper ej5x27r4"
> >
<div <div
class="components-base-control__field css-igk9ll-StyledField e1puf3u3" class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
> >
<label <label
class="components-base-control__label css-eweeby-StyledLabel-labelStyles e1puf3u2" class="components-base-control__label css-eweeby-StyledLabel-labelStyles ej5x27r2"
for="inspector-text-control-1" for="inspector-text-control-1"
> >
Search for items Search for items
@ -1424,13 +1424,13 @@ Object {
class="woocommerce-search-list__search" class="woocommerce-search-list__search"
> >
<div <div
class="components-base-control css-wdf2ti-Wrapper e1puf3u4" class="components-base-control css-wdf2ti-Wrapper ej5x27r4"
> >
<div <div
class="components-base-control__field css-igk9ll-StyledField e1puf3u3" class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
> >
<label <label
class="components-base-control__label css-eweeby-StyledLabel-labelStyles e1puf3u2" class="components-base-control__label css-eweeby-StyledLabel-labelStyles ej5x27r2"
for="inspector-text-control-9" for="inspector-text-control-9"
> >
Search for items Search for items
@ -1506,13 +1506,13 @@ Object {
class="woocommerce-search-list__search" class="woocommerce-search-list__search"
> >
<div <div
class="components-base-control css-wdf2ti-Wrapper e1puf3u4" class="components-base-control css-wdf2ti-Wrapper ej5x27r4"
> >
<div <div
class="components-base-control__field css-igk9ll-StyledField e1puf3u3" class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
> >
<label <label
class="components-base-control__label css-eweeby-StyledLabel-labelStyles e1puf3u2" class="components-base-control__label css-eweeby-StyledLabel-labelStyles ej5x27r2"
for="inspector-text-control-9" for="inspector-text-control-9"
> >
Search for items Search for items
@ -1669,13 +1669,13 @@ Object {
class="woocommerce-search-list__search" class="woocommerce-search-list__search"
> >
<div <div
class="components-base-control css-wdf2ti-Wrapper e1puf3u4" class="components-base-control css-wdf2ti-Wrapper ej5x27r4"
> >
<div <div
class="components-base-control__field css-igk9ll-StyledField e1puf3u3" class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
> >
<label <label
class="components-base-control__label css-eweeby-StyledLabel-labelStyles e1puf3u2" class="components-base-control__label css-eweeby-StyledLabel-labelStyles ej5x27r2"
for="inspector-text-control-8" for="inspector-text-control-8"
> >
Testing search label Testing search label
@ -1853,13 +1853,13 @@ Object {
class="woocommerce-search-list__search" class="woocommerce-search-list__search"
> >
<div <div
class="components-base-control css-wdf2ti-Wrapper e1puf3u4" class="components-base-control css-wdf2ti-Wrapper ej5x27r4"
> >
<div <div
class="components-base-control__field css-igk9ll-StyledField e1puf3u3" class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
> >
<label <label
class="components-base-control__label css-eweeby-StyledLabel-labelStyles e1puf3u2" class="components-base-control__label css-eweeby-StyledLabel-labelStyles ej5x27r2"
for="inspector-text-control-8" for="inspector-text-control-8"
> >
Testing search label Testing search label
@ -2118,13 +2118,13 @@ Object {
class="woocommerce-search-list__search" class="woocommerce-search-list__search"
> >
<div <div
class="components-base-control css-wdf2ti-Wrapper e1puf3u4" class="components-base-control css-wdf2ti-Wrapper ej5x27r4"
> >
<div <div
class="components-base-control__field css-igk9ll-StyledField e1puf3u3" class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
> >
<label <label
class="components-base-control__label css-eweeby-StyledLabel-labelStyles e1puf3u2" class="components-base-control__label css-eweeby-StyledLabel-labelStyles ej5x27r2"
for="inspector-text-control-4" for="inspector-text-control-4"
> >
Search for items Search for items
@ -2189,13 +2189,13 @@ Object {
class="woocommerce-search-list__search" class="woocommerce-search-list__search"
> >
<div <div
class="components-base-control css-wdf2ti-Wrapper e1puf3u4" class="components-base-control css-wdf2ti-Wrapper ej5x27r4"
> >
<div <div
class="components-base-control__field css-igk9ll-StyledField e1puf3u3" class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
> >
<label <label
class="components-base-control__label css-eweeby-StyledLabel-labelStyles e1puf3u2" class="components-base-control__label css-eweeby-StyledLabel-labelStyles ej5x27r2"
for="inspector-text-control-4" for="inspector-text-control-4"
> >
Search for items Search for items
@ -2341,13 +2341,13 @@ Object {
class="woocommerce-search-list__search" class="woocommerce-search-list__search"
> >
<div <div
class="components-base-control css-wdf2ti-Wrapper e1puf3u4" class="components-base-control css-wdf2ti-Wrapper ej5x27r4"
> >
<div <div
class="components-base-control__field css-igk9ll-StyledField e1puf3u3" class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
> >
<label <label
class="components-base-control__label css-eweeby-StyledLabel-labelStyles e1puf3u2" class="components-base-control__label css-eweeby-StyledLabel-labelStyles ej5x27r2"
for="inspector-text-control-7" for="inspector-text-control-7"
> >
Search for items Search for items
@ -2412,13 +2412,13 @@ Object {
class="woocommerce-search-list__search" class="woocommerce-search-list__search"
> >
<div <div
class="components-base-control css-wdf2ti-Wrapper e1puf3u4" class="components-base-control css-wdf2ti-Wrapper ej5x27r4"
> >
<div <div
class="components-base-control__field css-igk9ll-StyledField e1puf3u3" class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
> >
<label <label
class="components-base-control__label css-eweeby-StyledLabel-labelStyles e1puf3u2" class="components-base-control__label css-eweeby-StyledLabel-labelStyles ej5x27r2"
for="inspector-text-control-7" for="inspector-text-control-7"
> >
Search for items Search for items
@ -2564,13 +2564,13 @@ Object {
class="woocommerce-search-list__search" class="woocommerce-search-list__search"
> >
<div <div
class="components-base-control css-wdf2ti-Wrapper e1puf3u4" class="components-base-control css-wdf2ti-Wrapper ej5x27r4"
> >
<div <div
class="components-base-control__field css-igk9ll-StyledField e1puf3u3" class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
> >
<label <label
class="components-base-control__label css-eweeby-StyledLabel-labelStyles e1puf3u2" class="components-base-control__label css-eweeby-StyledLabel-labelStyles ej5x27r2"
for="inspector-text-control-5" for="inspector-text-control-5"
> >
Search for items Search for items
@ -2664,13 +2664,13 @@ Object {
class="woocommerce-search-list__search" class="woocommerce-search-list__search"
> >
<div <div
class="components-base-control css-wdf2ti-Wrapper e1puf3u4" class="components-base-control css-wdf2ti-Wrapper ej5x27r4"
> >
<div <div
class="components-base-control__field css-igk9ll-StyledField e1puf3u3" class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
> >
<label <label
class="components-base-control__label css-eweeby-StyledLabel-labelStyles e1puf3u2" class="components-base-control__label css-eweeby-StyledLabel-labelStyles ej5x27r2"
for="inspector-text-control-5" for="inspector-text-control-5"
> >
Search for items Search for items
@ -2845,13 +2845,13 @@ Object {
class="woocommerce-search-list__search" class="woocommerce-search-list__search"
> >
<div <div
class="components-base-control css-wdf2ti-Wrapper e1puf3u4" class="components-base-control css-wdf2ti-Wrapper ej5x27r4"
> >
<div <div
class="components-base-control__field css-igk9ll-StyledField e1puf3u3" class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
> >
<label <label
class="components-base-control__label css-eweeby-StyledLabel-labelStyles e1puf3u2" class="components-base-control__label css-eweeby-StyledLabel-labelStyles ej5x27r2"
for="inspector-text-control-6" for="inspector-text-control-6"
> >
Search for items Search for items
@ -2945,13 +2945,13 @@ Object {
class="woocommerce-search-list__search" class="woocommerce-search-list__search"
> >
<div <div
class="components-base-control css-wdf2ti-Wrapper e1puf3u4" class="components-base-control css-wdf2ti-Wrapper ej5x27r4"
> >
<div <div
class="components-base-control__field css-igk9ll-StyledField e1puf3u3" class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
> >
<label <label
class="components-base-control__label css-eweeby-StyledLabel-labelStyles e1puf3u2" class="components-base-control__label css-eweeby-StyledLabel-labelStyles ej5x27r2"
for="inspector-text-control-6" for="inspector-text-control-6"
> >
Search for items Search for items
@ -3176,13 +3176,13 @@ Object {
class="woocommerce-search-list__search" class="woocommerce-search-list__search"
> >
<div <div
class="components-base-control css-wdf2ti-Wrapper e1puf3u4" class="components-base-control css-wdf2ti-Wrapper ej5x27r4"
> >
<div <div
class="components-base-control__field css-igk9ll-StyledField e1puf3u3" class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
> >
<label <label
class="components-base-control__label css-eweeby-StyledLabel-labelStyles e1puf3u2" class="components-base-control__label css-eweeby-StyledLabel-labelStyles ej5x27r2"
for="inspector-text-control-2" for="inspector-text-control-2"
> >
Search for items Search for items
@ -3411,13 +3411,13 @@ Object {
class="woocommerce-search-list__search" class="woocommerce-search-list__search"
> >
<div <div
class="components-base-control css-wdf2ti-Wrapper e1puf3u4" class="components-base-control css-wdf2ti-Wrapper ej5x27r4"
> >
<div <div
class="components-base-control__field css-igk9ll-StyledField e1puf3u3" class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
> >
<label <label
class="components-base-control__label css-eweeby-StyledLabel-labelStyles e1puf3u2" class="components-base-control__label css-eweeby-StyledLabel-labelStyles ej5x27r2"
for="inspector-text-control-2" for="inspector-text-control-2"
> >
Search for items Search for items
@ -3768,13 +3768,13 @@ Object {
class="woocommerce-search-list__search" class="woocommerce-search-list__search"
> >
<div <div
class="components-base-control css-wdf2ti-Wrapper e1puf3u4" class="components-base-control css-wdf2ti-Wrapper ej5x27r4"
> >
<div <div
class="components-base-control__field css-igk9ll-StyledField e1puf3u3" class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
> >
<label <label
class="components-base-control__label css-eweeby-StyledLabel-labelStyles e1puf3u2" class="components-base-control__label css-eweeby-StyledLabel-labelStyles ej5x27r2"
for="inspector-text-control-3" for="inspector-text-control-3"
> >
Search for items Search for items
@ -4045,13 +4045,13 @@ Object {
class="woocommerce-search-list__search" class="woocommerce-search-list__search"
> >
<div <div
class="components-base-control css-wdf2ti-Wrapper e1puf3u4" class="components-base-control css-wdf2ti-Wrapper ej5x27r4"
> >
<div <div
class="components-base-control__field css-igk9ll-StyledField e1puf3u3" class="components-base-control__field css-10urnh1-StyledField-deprecatedMarginField ej5x27r3"
> >
<label <label
class="components-base-control__label css-eweeby-StyledLabel-labelStyles e1puf3u2" class="components-base-control__label css-eweeby-StyledLabel-labelStyles ej5x27r2"
for="inspector-text-control-3" for="inspector-text-control-3"
> >
Search for items Search for items

View File

@ -563,8 +563,8 @@ SelectControl.defaultProps = {
autoComplete: 'off', autoComplete: 'off',
}; };
export default compose( [ export default compose(
withSpokenMessages, withSpokenMessages,
withInstanceId, withInstanceId,
withFocusOutside, // this MUST be the innermost HOC as it calls handleFocusOutside withFocusOutside // this MUST be the innermost HOC as it calls handleFocusOutside
] )( SelectControl ); )( SelectControl );

View File

@ -169,7 +169,7 @@ describe( 'SelectControl', () => {
it( 'changes the options on search', async () => { it( 'changes the options on search', async () => {
const queriedOptions = []; const queriedOptions = [];
// eslint-disable-next-line no-shadow // eslint-disable-next-line @typescript-eslint/no-shadow
const queryOptions = ( options, searchedQuery ) => { const queryOptions = ( options, searchedQuery ) => {
if ( searchedQuery === 'test' ) { if ( searchedQuery === 'test' ) {
queriedOptions.push( { queriedOptions.push( {

View File

@ -1,7 +1,6 @@
/** /**
* External dependencies * External dependencies
*/ */
import { __ } from '@wordpress/i18n';
import { createElement, useContext } from '@wordpress/element'; import { createElement, useContext } from '@wordpress/element';
/** /**

View File

@ -1,7 +1,6 @@
/** /**
* External Dependencies * External Dependencies
*/ */
@import 'node_modules/@wordpress/base-styles/colors.native';
@import '@automattic/tour-kit/dist/esm/styles.scss'; @import '@automattic/tour-kit/dist/esm/styles.scss';
/** /**
@ -57,3 +56,5 @@
@import 'collapsible-content/style.scss'; @import 'collapsible-content/style.scss';
@import 'form/style.scss'; @import 'form/style.scss';
@import 'experimental-tree-control/tree.scss'; @import 'experimental-tree-control/tree.scss';
@import 'product-section-layout/style.scss';
@import 'tree-select-control/index.scss';

View File

@ -27,13 +27,14 @@ import TablePlaceholder from './placeholder';
import TableSummary, { TableSummaryPlaceholder } from './summary'; import TableSummary, { TableSummaryPlaceholder } from './summary';
import { TableCardProps } from './types'; import { TableCardProps } from './types';
const defaultOnQueryChange = const defaultOnQueryChange: (
( param: string ) => ( path?: string, direction?: string ) => {}; param: string
) => ( path?: string, direction?: string ) => void = () => () => {};
const defaultOnColumnsChange = ( const defaultOnColumnsChange: (
showCols: Array< string >, showCols: Array< string >,
key?: string key?: string
) => {}; ) => void = () => {};
/** /**
* This is an accessible, sortable, and scrollable table for displaying tabular data (like revenue and other analytics data). * This is an accessible, sortable, and scrollable table for displaying tabular data (like revenue and other analytics data).
* It accepts `headers` for column headers, and `rows` for the table content. * It accepts `headers` for column headers, and `rows` for the table content.

View File

@ -20,6 +20,7 @@ import { BACKSPACE } from './constants';
* @param {string} props.instanceId Id of the component * @param {string} props.instanceId Id of the component
* @param {string} props.placeholder Placeholder of the search input * @param {string} props.placeholder Placeholder of the search input
* @param {boolean} props.isExpanded True if the tree is expanded * @param {boolean} props.isExpanded True if the tree is expanded
* @param {boolean} props.alwaysShowPlaceholder Will always show placeholder (default: false)
* @param {boolean} props.disabled True if the component is disabled * @param {boolean} props.disabled True if the component is disabled
* @param {number} props.maxVisibleTags The maximum number of tags to show. Undefined, 0 or less than 0 evaluates to "Show All". * @param {number} props.maxVisibleTags The maximum number of tags to show. Undefined, 0 or less than 0 evaluates to "Show All".
* @param {string} props.value The current input value * @param {string} props.value The current input value
@ -43,11 +44,14 @@ const Control = forwardRef(
onTagsChange = () => {}, onTagsChange = () => {},
onInputChange = () => {}, onInputChange = () => {},
onControlClick = noop, onControlClick = noop,
alwaysShowPlaceholder = false,
}, },
ref ref
) => { ) => {
const hasTags = tags.length > 0; const hasTags = tags.length > 0;
const showPlaceholder = ! hasTags && ! isExpanded; const showPlaceholder = alwaysShowPlaceholder
? true
: ! hasTags && ! isExpanded;
/** /**
* Handles keydown event * Handles keydown event

View File

@ -23,7 +23,6 @@ import {
import useIsEqualRefValue from './useIsEqualRefValue'; import useIsEqualRefValue from './useIsEqualRefValue';
import Control from './control'; import Control from './control';
import Options from './options'; import Options from './options';
import './index.scss';
import { ARROW_DOWN, ARROW_UP, ENTER, ESCAPE, ROOT_VALUE } from './constants'; import { ARROW_DOWN, ARROW_UP, ENTER, ESCAPE, ROOT_VALUE } from './constants';
/** /**
@ -64,11 +63,14 @@ import { ARROW_DOWN, ARROW_UP, ENTER, ESCAPE, ROOT_VALUE } from './constants';
* @param {string} [props.placeholder] Placeholder for the search control input * @param {string} [props.placeholder] Placeholder for the search control input
* @param {string} [props.className] The class name for this component * @param {string} [props.className] The class name for this component
* @param {boolean} [props.disabled] Disables the component * @param {boolean} [props.disabled] Disables the component
* @param {boolean} [props.includeParent] Includes parent with selection.
* @param {boolean} [props.alwaysShowPlaceholder] Will always show placeholder (default: false)
* @param {Option[]} [props.options] Options to show in the component * @param {Option[]} [props.options] Options to show in the component
* @param {string[]} [props.value] Selected values * @param {string[]} [props.value] Selected values
* @param {number} [props.maxVisibleTags] The maximum number of tags to show. Undefined, 0 or less than 0 evaluates to "Show All". * @param {number} [props.maxVisibleTags] The maximum number of tags to show. Undefined, 0 or less than 0 evaluates to "Show All".
* @param {Function} [props.onChange] Callback when the selector changes * @param {Function} [props.onChange] Callback when the selector changes
* @param {(visible: boolean) => void} [props.onDropdownVisibilityChange] Callback when the visibility of the dropdown options is changed. * @param {(visible: boolean) => void} [props.onDropdownVisibilityChange] Callback when the visibility of the dropdown options is changed.
* @param {Function} [props.onInputChange] Callback when the selector changes
* @return {JSX.Element} The component * @return {JSX.Element} The component
*/ */
const TreeSelectControl = ( { const TreeSelectControl = ( {
@ -84,6 +86,9 @@ const TreeSelectControl = ( {
maxVisibleTags, maxVisibleTags,
onChange = () => {}, onChange = () => {},
onDropdownVisibilityChange = noop, onDropdownVisibilityChange = noop,
onInputChange = noop,
includeParent = false,
alwaysShowPlaceholder = false,
} ) => { } ) => {
let instanceId = useInstanceId( TreeSelectControl ); let instanceId = useInstanceId( TreeSelectControl );
instanceId = id ?? instanceId; instanceId = id ?? instanceId;
@ -204,7 +209,12 @@ const TreeSelectControl = ( {
return []; return [];
} }
return this.children.flatMap( ( option ) => { return this.children.flatMap( ( option ) => {
return option.hasChildren ? option.leaves : option; if ( option.hasChildren ) {
return includeParent && option.value !== ROOT_VALUE
? [ option, ...option.leaves ]
: option.leaves;
}
return option;
} ); } );
}, },
}, },
@ -217,6 +227,9 @@ const TreeSelectControl = ( {
* @return {boolean} True if checked, false otherwise. * @return {boolean} True if checked, false otherwise.
*/ */
get() { get() {
if ( includeParent && this.value !== ROOT_VALUE ) {
return cache.selectedValues.includes( this.value );
}
if ( this.hasChildren ) { if ( this.hasChildren ) {
return this.leaves.every( ( opt ) => opt.checked ); return this.leaves.every( ( opt ) => opt.checked );
} }
@ -372,37 +385,29 @@ const TreeSelectControl = ( {
); );
}; };
/**
* Handles a change on the Tree options. Could be a click on a parent option
* or a child option
*
* @param {boolean} checked Indicates if the item should be checked
* @param {InnerOption} option The option to change
*/
const handleOptionsChange = ( checked, option ) => {
if ( option.hasChildren ) {
handleParentChange( checked, option );
} else {
handleSingleChange( checked, option );
}
setInputControlValue( '' );
if ( ! nodesExpanded.includes( option.parent ) ) {
controlRef.current.focus();
}
};
/** /**
* Handles a change of a child element. * Handles a change of a child element.
* *
* @param {boolean} checked Indicates if the item should be checked * @param {boolean} checked Indicates if the item should be checked
* @param {InnerOption} option The option to change * @param {InnerOption} option The option to change
* @param {InnerOption} parent The options parent (could be null)
*/ */
const handleSingleChange = ( checked, option ) => { const handleSingleChange = ( checked, option, parent ) => {
const newValue = checked const newValue = checked
? [ ...value, option.value ] ? [ ...value, option.value ]
: value.filter( ( el ) => el !== option.value ); : value.filter( ( el ) => el !== option.value );
if (
includeParent &&
parent &&
parent.value !== ROOT_VALUE &&
parent.children &&
parent.children.every( ( child ) =>
newValue.includes( child.value )
) &&
! newValue.includes( parent.value )
) {
newValue.push( parent.value );
}
onChange( newValue ); onChange( newValue );
}; };
@ -417,7 +422,9 @@ const TreeSelectControl = ( {
const changedValues = option.leaves const changedValues = option.leaves
.filter( ( opt ) => opt.checked !== checked ) .filter( ( opt ) => opt.checked !== checked )
.map( ( opt ) => opt.value ); .map( ( opt ) => opt.value );
if ( includeParent && option.value !== ROOT_VALUE ) {
changedValues.push( option.value );
}
if ( checked ) { if ( checked ) {
if ( ! option.expanded ) { if ( ! option.expanded ) {
handleToggleExpanded( option ); handleToggleExpanded( option );
@ -430,6 +437,28 @@ const TreeSelectControl = ( {
onChange( newValue ); onChange( newValue );
}; };
/**
* Handles a change on the Tree options. Could be a click on a parent option
* or a child option
*
* @param {boolean} checked Indicates if the item should be checked
* @param {InnerOption} option The option to change
* @param {InnerOption} parent The options parent (could be null)
*/
const handleOptionsChange = ( checked, option, parent ) => {
if ( option.hasChildren ) {
handleParentChange( checked, option );
} else {
handleSingleChange( checked, option, parent );
}
onInputChange( '' );
setInputControlValue( '' );
if ( ! nodesExpanded.includes( option.parent ) ) {
controlRef.current.focus();
}
};
/** /**
* Handles a change of a Tag element. We map them to Value format. * Handles a change of a Tag element. We map them to Value format.
* *
@ -446,6 +475,7 @@ const TreeSelectControl = ( {
* @param {Event} e Event returned by the On Change function in the Input control * @param {Event} e Event returned by the On Change function in the Input control
*/ */
const handleOnInputChange = ( e ) => { const handleOnInputChange = ( e ) => {
onInputChange( e.target.value );
setInputControlValue( e.target.value ); setInputControlValue( e.target.value );
}; };
@ -486,6 +516,7 @@ const TreeSelectControl = ( {
value={ inputControlValue } value={ inputControlValue }
onTagsChange={ handleTagsChange } onTagsChange={ handleTagsChange }
onInputChange={ handleOnInputChange } onInputChange={ handleOnInputChange }
alwaysShowPlaceholder={ alwaysShowPlaceholder }
/> />
{ showTree && ( { showTree && (
<div <div

View File

@ -22,6 +22,7 @@ import Checkbox from './checkbox';
* *
* @param {Object} props Component parameters * @param {Object} props Component parameters
* @param {InnerOption[]} props.options List of options to be rendered * @param {InnerOption[]} props.options List of options to be rendered
* @param {InnerOption} props.parent Parent option
* @param {Function} props.onChange Callback when an option changes * @param {Function} props.onChange Callback when an option changes
* @param {Function} [props.onExpanderClick] Callback when an expander is clicked. * @param {Function} [props.onExpanderClick] Callback when an expander is clicked.
* @param {(option: InnerOption) => void} [props.onToggleExpanded] Callback when requesting an expander to be toggled. * @param {(option: InnerOption) => void} [props.onToggleExpanded] Callback when requesting an expander to be toggled.
@ -31,6 +32,7 @@ const Options = ( {
onChange = () => {}, onChange = () => {},
onExpanderClick = noop, onExpanderClick = noop,
onToggleExpanded = noop, onToggleExpanded = noop,
parent = null,
} ) => { } ) => {
/** /**
* Alters the node with some keys for accessibility * Alters the node with some keys for accessibility
@ -76,6 +78,7 @@ const Options = ( {
) } ) }
tabIndex="-1" tabIndex="-1"
onClick={ ( e ) => { onClick={ ( e ) => {
e.preventDefault();
onExpanderClick( e ); onExpanderClick( e );
onToggleExpanded( option ); onToggleExpanded( option );
} } } }
@ -93,7 +96,7 @@ const Options = ( {
option={ option } option={ option }
checked={ checked } checked={ checked }
onChange={ ( e ) => { onChange={ ( e ) => {
onChange( e.target.checked, option ); onChange( e.target.checked, option, parent );
} } } }
onKeyDown={ ( e ) => { onKeyDown={ ( e ) => {
handleKeyDown( e, option ); handleKeyDown( e, option );
@ -113,6 +116,7 @@ const Options = ( {
onChange={ onChange } onChange={ onChange }
onExpanderClick={ onExpanderClick } onExpanderClick={ onExpanderClick }
onToggleExpanded={ onToggleExpanded } onToggleExpanded={ onToggleExpanded }
parent={ option }
/> />
</div> </div>
) } ) }

View File

@ -1,7 +1,7 @@
/** /**
* External dependencies * External dependencies
*/ */
import { useState } from '@wordpress/element'; import { useEffect, useState } from '@wordpress/element';
/** /**
* Internal dependencies * Internal dependencies
@ -15,7 +15,7 @@ const treeSelectControlOptions = [
children: [ children: [
{ value: 'ES', label: 'Spain' }, { value: 'ES', label: 'Spain' },
{ value: 'FR', label: 'France' }, { value: 'FR', label: 'France' },
{ key: 'FR-Colonies', value: 'FR', label: 'France (Colonies)' }, { key: 'FR-Colonies', value: 'FR-C', label: 'France (Colonies)' },
], ],
}, },
{ {
@ -69,6 +69,12 @@ const treeSelectControlOptions = [
const Template = ( args ) => { const Template = ( args ) => {
const [ selected, setSelected ] = useState( [ 'ES' ] ); const [ selected, setSelected ] = useState( [ 'ES' ] );
useEffect( () => {
if ( args.onChange ) {
args.onChange( selected );
}
}, [ selected ] );
return ( return (
<TreeSelectControl <TreeSelectControl
{ ...args } { ...args }
@ -88,6 +94,13 @@ Base.args = {
options: treeSelectControlOptions, options: treeSelectControlOptions,
maxVisibleTags: 3, maxVisibleTags: 3,
selectAllLabel: 'All countries', selectAllLabel: 'All countries',
includeParent: false,
alwaysShowPlaceholder: false,
};
Base.argTypes = {
onInputChange: { action: 'onInputChange' },
onChange: { action: 'onChange' },
}; };
export default { export default {

View File

@ -110,4 +110,36 @@ describe( 'TreeSelectControl - Control Component', () => {
placeholder = input.getAttribute( 'placeholder' ); placeholder = input.getAttribute( 'placeholder' );
expect( placeholder ).toBeFalsy(); expect( placeholder ).toBeFalsy();
} ); } );
it( 'Renders placeholder when alwaysShowPlaceholder is true with tags or expanded', () => {
const { rerender } = render(
<Control placeholder="Select" alwaysShowPlaceholder={ true } />
);
let input = screen.queryByRole( 'combobox' );
let placeholder = input.getAttribute( 'placeholder' );
expect( placeholder ).toBe( 'Select' );
rerender(
<Control
placeholder="Select"
alwaysShowPlaceholder={ true }
tags={ [ { id: 'es', label: 'Spain' } ] }
/>
);
input = screen.queryByRole( 'combobox' );
placeholder = input.getAttribute( 'placeholder' );
expect( placeholder ).toBeTruthy();
rerender(
<Control
placeholder="Select"
isExpanded={ true }
alwaysShowPlaceholder={ true }
/>
);
input = screen.queryByRole( 'combobox' );
placeholder = input.getAttribute( 'placeholder' );
expect( placeholder ).toBeTruthy();
} );
} ); } );

View File

@ -71,6 +71,25 @@ describe( 'TreeSelectControl Component', () => {
expect( onChange ).toHaveBeenCalledWith( [ 'ES', 'AS' ] ); expect( onChange ).toHaveBeenCalledWith( [ 'ES', 'AS' ] );
} ); } );
it( 'Should include parent in onChange value when includeParent is truthy', () => {
const onChange = jest.fn().mockName( 'onChange' );
const { queryByLabelText, queryByRole } = render(
<TreeSelectControl
options={ options }
value={ [] }
onChange={ onChange }
includeParent={ true }
/>
);
const control = queryByRole( 'combobox' );
fireEvent.click( control );
const checkbox = queryByLabelText( 'Europe' );
fireEvent.click( checkbox );
expect( onChange ).toHaveBeenCalledWith( [ 'ES', 'FR', 'IT', 'EU' ] );
} );
it( 'Renders the label', () => { it( 'Renders the label', () => {
const { queryByLabelText } = render( const { queryByLabelText } = render(
<TreeSelectControl options={ options } label="Select" /> <TreeSelectControl options={ options } label="Select" />
@ -148,4 +167,25 @@ describe( 'TreeSelectControl Component', () => {
expect( queryByLabelText( 'France' ) ).toBeFalsy(); // doesn't match pain expect( queryByLabelText( 'France' ) ).toBeFalsy(); // doesn't match pain
expect( queryByLabelText( 'Asia' ) ).toBeFalsy(); // doesn't match pain expect( queryByLabelText( 'Asia' ) ).toBeFalsy(); // doesn't match pain
} ); } );
it( 'should call onInputChange when input field changed', () => {
const onInputChangeMock = jest.fn();
const { queryByRole } = render(
<TreeSelectControl
options={ options }
onInputChange={ onInputChangeMock }
/>
);
const control = queryByRole( 'combobox' );
fireEvent.click( control );
fireEvent.change( control, { target: { value: 'Asi' } } );
expect( onInputChangeMock ).toHaveBeenCalledWith( 'Asi' );
fireEvent.change( control, { target: { value: 'As' } } );
expect( onInputChangeMock ).toHaveBeenCalledWith( 'As' );
fireEvent.change( control, { target: { value: 'pain' } } );
expect( onInputChangeMock ).toHaveBeenCalledWith( 'pain' );
} );
} ); } );

View File

@ -5,12 +5,58 @@ import { isValidElement, Fragment } from 'react';
import { Slot, Fill } from '@wordpress/components'; import { Slot, Fill } from '@wordpress/components';
import { cloneElement, createElement } from '@wordpress/element'; import { cloneElement, createElement } from '@wordpress/element';
type ChildrenProps = {
order: number;
};
/**
* Returns an object with the children and props that will be used by `cloneElement`. They will change depending on the
* type of children passed in.
*
* @param {Node} children - Node children.
* @param {number} order - Node order.
* @param {Array} props - Fill props.
* @param {Object} injectProps - Props to inject.
* @return {Object} Object with the keys: children and props.
*/
function getChildrenAndProps< T = Fill.Props, S = Record< string, unknown > >(
children: React.ReactNode,
order: number,
props: T,
injectProps?: S
) {
if ( typeof children === 'function' ) {
return {
children: children( { ...props, order, ...injectProps } ),
props: { order, ...injectProps },
};
} else if ( isValidElement( children ) ) {
// This checks whether 'children' is a react element or a standard HTML element.
if ( typeof children?.type === 'function' ) {
return {
children,
props: {
...props,
order,
...injectProps,
},
};
}
return {
children: children as React.ReactElement< ChildrenProps >,
props: { order, ...injectProps },
};
}
throw Error( 'Invalid children type' );
}
/** /**
* Ordered fill item. * Ordered fill item.
* *
* @param {Node} children - Node children. * @param {Node} children - Node children.
* @param {number} order - Node order. * @param {number} order - Node order.
* @param {Array} props - Fill props. * @param {Array} props - Fill props.
* @param {Object} injectProps - Props to inject.
* @return {Node} Node. * @return {Node} Node.
*/ */
function createOrderedChildren< T = Fill.Props, S = Record< string, unknown > >( function createOrderedChildren< T = Fill.Props, S = Record< string, unknown > >(
@ -19,15 +65,9 @@ function createOrderedChildren< T = Fill.Props, S = Record< string, unknown > >(
props: T, props: T,
injectProps?: S injectProps?: S
) { ) {
if ( typeof children === 'function' ) { const { children: childrenToRender, props: propsToRender } =
return cloneElement( children( { ...props, order, ...injectProps } ), { getChildrenAndProps( children, order, props, injectProps );
order, return cloneElement( childrenToRender, propsToRender );
...injectProps,
} );
} else if ( isValidElement( children ) ) {
return cloneElement( children, { ...props, order, ...injectProps } );
}
throw Error( 'Invalid children type' );
} }
export { createOrderedChildren }; export { createOrderedChildren };

View File

@ -27,16 +27,16 @@ A Slotfill component that will allow you to add a new field to a specific sectio
This is the fill component. You must provide the `id` prop to identify your product field fill with a unique string. This component will accept a series of props: This is the fill component. You must provide the `id` prop to identify your product field fill with a unique string. This component will accept a series of props:
| Prop | Type | Description | | Prop | Type | Description |
| -------------| -------- | ------------------------------------------------------------------------------------------------------------------------ | | ---------- | ------ | ------------------------------------------------------------------------------------------------ |
| `id` | String | A unique string to identify your fill. Used for configuiration management. | | `id` | String | A unique string to identify your fill. Used for configuration management. |
| `section ` | String | The string used to identify the particular section where you want to render your field. | | `sections` | Array | Contains an array of name and order values for which slots it should be rendered in. |
| `pluginId` | String | A unique plugin ID to identify the plugin/extension that this fill is associated with. | | `pluginId` | String | A unique plugin ID to identify the plugin/extension that this fill is associated with. |
| `order` | Number | (optional) This number will dictate the order that the fields rendered by a Slot will be appear. | | `order` | Number | (optional) This number will dictate the order that the fields rendered by a Slot will be appear. |
### WooProductFieldItem.Slot (slot) ### WooProductFieldItem.Slot (slot)
This is the slot component, and will not be used as frequently. It must also receive the required `location` prop that will be identical to the fill `location`. This is the slot component. This will render all the registered fills that match the `section` prop.
| Name | Type | Description | | Name | Type | Description |
| ----------- | ------ | ---------------------------------------------------------------------------------------------------- | | --------- | ------ | --------------------------------------------------------------------------------------------------- |
| `section` | String | Unique to the section that the Slot appears, and must be the same as the one provided to any fills. | | `section` | String | Unique to the section that the Slot appears, and must be the same as the one provided to any fills. |

View File

@ -4,6 +4,7 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { Slot, Fill } from '@wordpress/components'; import { Slot, Fill } from '@wordpress/components';
import { createElement, Children, Fragment } from '@wordpress/element'; import { createElement, Children, Fragment } from '@wordpress/element';
import deprecated from '@wordpress/deprecated';
/** /**
* Internal dependencies * Internal dependencies
@ -29,6 +30,8 @@ type WooProductFieldFillProps = {
children?: React.ReactNode; children?: React.ReactNode;
}; };
const DEFAULT_FIELD_ORDER = 20;
const WooProductFieldFill: React.FC< WooProductFieldFillProps > = ( { const WooProductFieldFill: React.FC< WooProductFieldFillProps > = ( {
fieldName, fieldName,
sectionName, sectionName,
@ -39,6 +42,12 @@ const WooProductFieldFill: React.FC< WooProductFieldFillProps > = ( {
const fieldId = `product_field/${ sectionName }/${ fieldName }`; const fieldId = `product_field/${ sectionName }/${ fieldName }`;
deprecated( `__experimentalWooProductFieldItem`, {
version: '13.0.0',
plugin: '@woocommerce/components',
hint: 'Moved to @woocommerce/product-editor package: import { __experimentalWooProductFieldItem } from @woocommerce/product-editor',
} );
useEffect( () => { useEffect( () => {
registerFill( fieldId ); registerFill( fieldId );
}, [] ); }, [] );
@ -75,7 +84,8 @@ export const WooProductFieldItem: React.FC< WooProductFieldItemProps > & {
} = ( { children, sections, id } ) => { } = ( { children, sections, id } ) => {
return ( return (
<> <>
{ sections.map( ( { name: sectionName, order = 20 } ) => ( { sections.map(
( { name: sectionName, order = DEFAULT_FIELD_ORDER } ) => (
<WooProductFieldFill <WooProductFieldFill
fieldName={ id } fieldName={ id }
sectionName={ sectionName } sectionName={ sectionName }
@ -84,7 +94,8 @@ export const WooProductFieldItem: React.FC< WooProductFieldItemProps > & {
> >
{ children } { children }
</WooProductFieldFill> </WooProductFieldFill>
) ) } )
) }
</> </>
); );
}; };

View File

@ -31,16 +31,16 @@ A Slotfill component that will allow you to add a new section to the product edi
This is the fill component. You must provide the `id` prop to identify your section fill with a unique string. This component will accept a series of props: This is the fill component. You must provide the `id` prop to identify your section fill with a unique string. This component will accept a series of props:
| Prop | Type | Description | | Prop | Type | Description |
| -------------| -------- | ------------------------------------------------------------------------------------------------------------------------ | | ---------- | ------ | -------------------------------------------------------------------------------------------------- |
| `id` | String | A unique string to identify your fill. Used for configuiration management. | | `id` | String | A unique string to identify your fill. Used for configuiration management. |
| `location` | String | The string used to identify the particular location that you want to render your section. | | `tabs` | Array | Contains an array of name and order of which slots it should be rendered in. |
| `pluginId` | String | A unique plugin ID to identify the plugin/extension that this fill is associated with. | | `pluginId` | String | A unique plugin ID to identify the plugin/extension that this fill is associated with. |
| `order` | Number | (optional) This number will dictate the order that the sections rendered by a Slot will be appear. | | `order` | Number | (optional) This number will dictate the order that the sections rendered by a Slot will be appear. |
### WooProductSectionItem.Slot (slot) ### WooProductSectionItem.Slot (slot)
This is the slot component, and will not be used as frequently. It must also receive the required `location` prop that will be identical to the fill `location`. This is the slot component. This will render all the registered fills that match the `tab` prop.
| Name | Type | Description | | Name | Type | Description |
| ----------- | ------ | ---------------------------------------------------------------------------------------------------- | | ----- | ------ | ---------------------------------------------------------------------------------------------------- |
| `location` | String | Unique to the location that the Slot appears, and must be the same as the one provided to any fills. | | `tab` | String | Unique to the location that the Slot appears, and must be the same as the one provided to any fills. |

View File

@ -4,6 +4,7 @@
import React from 'react'; import React from 'react';
import { Slot, Fill } from '@wordpress/components'; import { Slot, Fill } from '@wordpress/components';
import { createElement, Fragment } from '@wordpress/element'; import { createElement, Fragment } from '@wordpress/element';
import deprecated from '@wordpress/deprecated';
/** /**
* Internal dependencies * Internal dependencies
@ -21,12 +22,20 @@ type WooProductSectionSlotProps = {
tab: string; tab: string;
}; };
const DEFAULT_SECTION_ORDER = 20;
export const WooProductSectionItem: React.FC< WooProductSectionItemProps > & { export const WooProductSectionItem: React.FC< WooProductSectionItemProps > & {
Slot: React.FC< Slot.Props & WooProductSectionSlotProps >; Slot: React.FC< Slot.Props & WooProductSectionSlotProps >;
} = ( { children, tabs } ) => { } = ( { children, tabs } ) => {
deprecated( `__experimentalWooProductSectionItem`, {
version: '13.0.0',
plugin: '@woocommerce/components',
hint: 'Moved to @woocommerce/product-editor package: import { __experimentalWooProductSectionItem } from @woocommerce/product-editor',
} );
return ( return (
<> <>
{ tabs.map( ( { name: tabName, order: tabOrder } ) => ( { tabs.map( ( { name: tabName, order: sectionOrder } ) => (
<Fill <Fill
name={ `woocommerce_product_section_${ tabName }` } name={ `woocommerce_product_section_${ tabName }` }
key={ tabName } key={ tabName }
@ -34,7 +43,7 @@ export const WooProductSectionItem: React.FC< WooProductSectionItemProps > & {
{ ( fillProps: Fill.Props ) => { { ( fillProps: Fill.Props ) => {
return createOrderedChildren< return createOrderedChildren<
Fill.Props & { tabName: string } Fill.Props & { tabName: string }
>( children, tabOrder || 20, { >( children, sectionOrder || DEFAULT_SECTION_ORDER, {
tabName, tabName,
...fillProps, ...fillProps,
} ); } );

View File

@ -19,17 +19,17 @@ A Slotfill component that will allow you to add a new tab to the product editor.
This is the fill component. You must provide the `id` prop to identify your section fill with a unique string. This component will accept a series of props: This is the fill component. You must provide the `id` prop to identify your section fill with a unique string. This component will accept a series of props:
| Prop | Type | Description | | Prop | Type | Description |
| ---------- | ------ | -------------------------------------------------------------------------------------------------------------- | | ----------- | ------ | -------------------------------------------------------------------------------------------------------------- |
| `id` | String | A unique string to identify your fill. Used for configuiration management. | | `id` | String | A unique string to identify your fill. Used for configuiration management. |
| `location` | String | The string used to identify the particular location that you want to render your section. | | `templates` | Array | Array of name and order of which template slots it should be rendered in |
| `pluginId` | String | A unique plugin ID to identify the plugin/extension that this fill is associated with. | | `pluginId` | String | A unique plugin ID to identify the plugin/extension that this fill is associated with. |
| `tabProps` | Object | An object containing tab props: name, title, className, disabled (see TabPanel.Tab from @wordpress/components) | | `tabProps` | Object | An object containing tab props: name, title, className, disabled (see TabPanel.Tab from @wordpress/components) |
| `order` | Number | (optional) This number will dictate the order that the sections rendered by a Slot will be appear. | | `order` | Number | (optional) This number will dictate the order that the sections rendered by a Slot will be appear. |
### WooProductTabItem.Slot (slot) ### WooProductTabItem.Slot (slot)
This is the slot component, and will not be used as frequently. It must also receive the required `location` prop that will be identical to the fill `location`. This is the slot component. This will render all the registered fills that match the `template` prop.
| Name | Type | Description | | Name | Type | Description |
| ---------- | ------ | ---------------------------------------------------------------------------------------------------- | | ---------- | ------ | ---------------------------------------------------------------------------------------------------- |
| `location` | String | Unique to the location that the Slot appears, and must be the same as the one provided to any fills. | | `template` | String | Unique to the location that the Slot appears, and must be the same as the one provided to any fills. |

View File

@ -4,6 +4,7 @@
import React, { ReactElement, ReactNode } from 'react'; import React, { ReactElement, ReactNode } from 'react';
import { Slot, Fill, TabPanel } from '@wordpress/components'; import { Slot, Fill, TabPanel } from '@wordpress/components';
import { createElement, Fragment } from '@wordpress/element'; import { createElement, Fragment } from '@wordpress/element';
import deprecated from '@wordpress/deprecated';
/** /**
* Internal dependencies * Internal dependencies
@ -30,11 +31,19 @@ type WooProductFieldSlotProps = {
) => ReactElement | null; ) => ReactElement | null;
}; };
const DEFAULT_TAB_ORDER = 20;
export const WooProductTabItem: React.FC< WooProductTabItemProps > & { export const WooProductTabItem: React.FC< WooProductTabItemProps > & {
Slot: React.VFC< Slot: React.VFC<
Omit< Slot.Props, 'children' > & WooProductFieldSlotProps Omit< Slot.Props, 'children' > & WooProductFieldSlotProps
>; >;
} = ( { children, tabProps, templates } ) => { } = ( { children, tabProps, templates } ) => {
deprecated( `__experimentalWooProductTabItem`, {
version: '13.0.0',
plugin: '@woocommerce/components',
hint: 'Moved to @woocommerce/product-editor package: import { __experimentalWooProductTabItem } from @woocommerce/product-editor',
} );
if ( ! templates ) { if ( ! templates ) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.warn( 'WooProductTabItem fill is missing templates property.' ); console.warn( 'WooProductTabItem fill is missing templates property.' );
@ -50,12 +59,12 @@ export const WooProductTabItem: React.FC< WooProductTabItemProps > & {
{ ( fillProps: Fill.Props ) => { { ( fillProps: Fill.Props ) => {
return createOrderedChildren< Fill.Props >( return createOrderedChildren< Fill.Props >(
children, children,
templateData.order || 20, templateData.order || DEFAULT_TAB_ORDER,
{}, {},
{ {
tabProps, tabProps,
templateName: templateData.name, templateName: templateData.name,
order: templateData.order || 20, order: templateData.order || DEFAULT_TAB_ORDER,
...fillProps, ...fillProps,
} }
); );
@ -84,7 +93,7 @@ WooProductTabItem.Slot = ( { fillProps, template, children } ) => (
: props.tabProps; : props.tabProps;
tabs.push( { tabs.push( {
...tabProps, ...tabProps,
order: props.order ?? 20, order: props.order ?? DEFAULT_TAB_ORDER,
} ); } );
} }
return { return {

View File

@ -2,6 +2,12 @@
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.2](https://www.npmjs.com/package/@woocommerce/create-woo-extension/v/1.0.2) - 2023-02-13
- Patch - Add examples of Woo components package [#36732]
- Patch - bump WooCommerce version [#36732]
- Patch - Update readme [#36732]
## [1.0.1](https://www.npmjs.com/package/@woocommerce/create-woo-extension/v/1.0.1) - 2022-12-20 ## [1.0.1](https://www.npmjs.com/package/@woocommerce/create-woo-extension/v/1.0.1) - 2022-12-20
- Patch - Fix install scripts [#34385] - Patch - Fix install scripts [#34385]

View File

@ -1,4 +0,0 @@
Significance: patch
Type: add
Add examples of Woo components package

View File

@ -1,3 +0,0 @@
Significance: patch
Type: dev
Comment: Just some PHP clean up to adhere to coding standards

View File

@ -1,5 +0,0 @@
Significance: patch
Type: fix
Comment: Dev dependency update.

View File

@ -1,4 +0,0 @@
Significance: patch
Type: dev
Update readme

View File

@ -1,4 +0,0 @@
Significance: patch
Type: update
bump WooCommerce version

View File

@ -1,6 +1,6 @@
{ {
"name": "@woocommerce/create-woo-extension", "name": "@woocommerce/create-woo-extension",
"version": "1.0.1", "version": "1.0.2",
"description": "A template to be used with `@wordpress/create-block` to create a WooCommerce extension.", "description": "A template to be used with `@wordpress/create-block` to create a WooCommerce extension.",
"main": "index.js", "main": "index.js",
"engines": { "engines": {

View File

@ -0,0 +1,4 @@
Significance: minor
Type: dev
Sync @wordpress package versions via syncpack.

View File

@ -1,4 +1,4 @@
Significance: minor Significance: minor
Type: update Type: update
Add default value for backorders Adding currencyContext component.

View File

@ -27,10 +27,11 @@
"react-native": "src/index", "react-native": "src/index",
"dependencies": { "dependencies": {
"@woocommerce/number": "workspace:*", "@woocommerce/number": "workspace:*",
"@wordpress/deprecated": "^2.12.3", "@wordpress/deprecated": "wp-6.0",
"@wordpress/element": "^4.1.1", "@wordpress/element": "wp-6.0",
"@wordpress/html-entities": "^3.3.1", "@wordpress/hooks": "wp-6.0",
"@wordpress/i18n": "^3.20.0" "@wordpress/html-entities": "wp-6.0",
"@wordpress/i18n": "wp-6.0"
}, },
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"

View File

@ -3,14 +3,15 @@
*/ */
import { createContext } from '@wordpress/element'; import { createContext } from '@wordpress/element';
import { applyFilters } from '@wordpress/hooks'; import { applyFilters } from '@wordpress/hooks';
import CurrencyFactory from '@woocommerce/currency'; import { getSetting } from '@woocommerce/settings';
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { CURRENCY } from '~/utils/admin-settings'; import { CurrencyFactory } from './index';
const CURRENCY = getSetting( 'currency' );
const appCurrency = CurrencyFactory( CURRENCY ); const appCurrency = CurrencyFactory( CURRENCY );
export const getFilteredCurrencyInstance = ( query ) => { export const getFilteredCurrencyInstance = ( query ) => {
const config = appCurrency.getCurrencyConfig(); const config = appCurrency.getCurrencyConfig();
/** /**

View File

@ -0,0 +1,9 @@
/**
* Internal dependencies
*/
import { CurrencyFactory } from './utils';
export default CurrencyFactory;
export * from './utils';
export * from './currency-context';

View File

@ -62,7 +62,7 @@ export type CountryInfo = {
* @param {CurrencyConfig} currencySetting * @param {CurrencyConfig} currencySetting
* @return {Object} currency object * @return {Object} currency object
*/ */
const CurrencyFactory = function ( currencySetting?: CurrencyConfig ) { const CurrencyFactoryBase = function ( currencySetting?: CurrencyConfig ) {
let currency: Currency; let currency: Currency;
function stripTags( str: string ) { function stripTags( str: string ) {
@ -272,7 +272,7 @@ const CurrencyFactory = function ( currencySetting?: CurrencyConfig ) {
}; };
}; };
export default CurrencyFactory; export const CurrencyFactory = CurrencyFactoryBase;
/** /**
* Returns currency data by country/region. Contains code, symbol, position, thousands separator, decimal separator, and precision. * Returns currency data by country/region. Contains code, symbol, position, thousands separator, decimal separator, and precision.

Some files were not shown because too many files have changed in this diff Show More