Merge branch 'trunk' into feature/34548-multichannel-marketing-backend
This commit is contained in:
commit
ab8eca2733
|
@ -14,18 +14,27 @@
|
||||||
|
|
||||||
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 -->
|
||||||
|
|
||||||
### 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. -->
|
||||||
|
|
||||||
1.
|
1.
|
||||||
2.
|
2.
|
||||||
3.
|
3.
|
||||||
|
|
||||||
|
<!-- End testing instructions -->
|
||||||
|
|
||||||
### Other information:
|
### Other information:
|
||||||
|
|
||||||
- [ ] 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 successfully run tests with your changes locally?
|
- [ ] 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> run changelog add`?
|
|
||||||
|
|
||||||
<!-- Mark completed items with an [x] -->
|
<!-- Mark completed items with an [x] -->
|
||||||
|
|
||||||
|
|
|
@ -35,12 +35,14 @@ runs:
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: shivammathur/setup-php@e04e1d97f0c0481c6e1ba40f8a538454fe5d7709
|
uses: shivammathur/setup-php@e04e1d97f0c0481c6e1ba40f8a538454fe5d7709
|
||||||
with:
|
with:
|
||||||
php-version: ${{ inputs.php-version }}
|
php-version: ${{ inputs.php-version }}
|
||||||
coverage: none
|
coverage: none
|
||||||
|
tools: phpcs, sirbrillig/phpcs-changed
|
||||||
|
|
||||||
- name: Cache Composer Dependencies
|
- name: Cache Composer Dependencies
|
||||||
uses: actions/cache@fd5de65bc895cf536527842281bea11763fefd77
|
uses: actions/cache@fd5de65bc895cf536527842281bea11763fefd77
|
||||||
|
|
|
@ -1,3 +1,85 @@
|
||||||
|
# See https://github.com/shufo/auto-assign-reviewer-by-files/blob/main/README.md for configuration format
|
||||||
|
|
||||||
".github/*":
|
".github/*":
|
||||||
- atlas
|
- team: atlas
|
||||||
|
|
||||||
|
"packages/js/api/**/*":
|
||||||
|
- team: solaris
|
||||||
|
|
||||||
|
"packages/js/e2e-utils/**/*":
|
||||||
|
- team: solaris
|
||||||
|
|
||||||
|
"packages/js/e2e-environment/**/*":
|
||||||
|
- team: solaris
|
||||||
|
|
||||||
|
"packages/js/api-core-tests/**/*":
|
||||||
|
- team: solaris
|
||||||
|
|
||||||
|
"packages/js/e2e-core-tests/**/*":
|
||||||
|
- team: solaris
|
||||||
|
|
||||||
|
"packages/js/admin-e2e-tests/**/*":
|
||||||
|
- team: solaris
|
||||||
|
|
||||||
|
"packages/js/components/**/*":
|
||||||
|
- team: mothra
|
||||||
|
- team: ghidorah
|
||||||
|
|
||||||
|
"packages/js/csv-export/**/*":
|
||||||
|
- team: mothra
|
||||||
|
|
||||||
|
"packages/js/currency/**/*":
|
||||||
|
- team: mothra
|
||||||
|
|
||||||
|
"packages/js/customer-effort-score/**/*":
|
||||||
|
- team: mothra
|
||||||
|
|
||||||
|
"packages/js/data/**/*":
|
||||||
|
- team: mothra
|
||||||
|
- team: ghidorah
|
||||||
|
|
||||||
|
"packages/js/date/**/*":
|
||||||
|
- team: mothra
|
||||||
|
|
||||||
|
"packages/js/dependency-extraction-webpack-plugin/**/*":
|
||||||
|
- team: mothra
|
||||||
|
|
||||||
|
"packages/js/eslint-plugin/**/*":
|
||||||
|
- team: mothra
|
||||||
|
|
||||||
|
"packages/js/experimental/**/*":
|
||||||
|
- team: mothra
|
||||||
|
|
||||||
|
"packages/js/explat/**/*":
|
||||||
|
- team: mothra
|
||||||
|
- team: ghidorah
|
||||||
|
|
||||||
|
"packages/js/navigation/**/*":
|
||||||
|
- team: mothra
|
||||||
|
|
||||||
|
"packages/js/number/**/*":
|
||||||
|
- team: mothra
|
||||||
|
|
||||||
|
"packages/js/onboarding/**/*":
|
||||||
|
- team: ghidorah
|
||||||
|
|
||||||
|
"packages/js/tracks/**/*":
|
||||||
|
- team: mothra
|
||||||
|
|
||||||
|
"plugins/woocommerce/**/*":
|
||||||
|
- team: proton
|
||||||
|
|
||||||
|
"plugins/woocommerce/src/Admin/**/*":
|
||||||
|
- team: mothra
|
||||||
|
- team: ghidorah
|
||||||
|
|
||||||
|
"plugins/woocommerce/src/Internal/Admin/**/*":
|
||||||
|
- team: mothra
|
||||||
|
- team: ghidorah
|
||||||
|
|
||||||
|
"plugins/woocommerce-admin/**/*":
|
||||||
|
- team: mothra
|
||||||
|
- team: ghidorah
|
||||||
|
|
||||||
|
"plugins/woocommerce-beta-tester/**/*":
|
||||||
|
- team: atlas
|
||||||
|
|
|
@ -27,3 +27,19 @@ jobs:
|
||||||
asset_path: plugins/woocommerce/woocommerce.zip
|
asset_path: plugins/woocommerce/woocommerce.zip
|
||||||
asset_name: woocommerce.zip
|
asset_name: woocommerce.zip
|
||||||
asset_content_type: application/zip
|
asset_content_type: application/zip
|
||||||
|
|
||||||
|
|
||||||
|
update-code-reference:
|
||||||
|
if: github.event.release.prerelease == false && github.event.release.draft == false && github.repository_owner == 'woocommerce'
|
||||||
|
name: Update Code Reference
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
steps:
|
||||||
|
- name: Invoke Code Reference build and deploy workflow
|
||||||
|
uses: aurelien-baudet/workflow-dispatch@v2
|
||||||
|
with:
|
||||||
|
workflow: GitHub Pages deploy
|
||||||
|
repo: woocommerce/code-reference
|
||||||
|
token: ${{ secrets.CUSTOM_GH_TOKEN }}
|
||||||
|
ref: refs/heads/trunk
|
||||||
|
inputs: '{ "version": "${{ github.event.release.tag_name }}" }'
|
||||||
|
|
|
@ -211,45 +211,45 @@ jobs:
|
||||||
if ( changelogEntry.match( /comment:/i ) ) {
|
if ( changelogEntry.match( /comment:/i ) ) {
|
||||||
changelogEntry = false;
|
changelogEntry = false;
|
||||||
}
|
}
|
||||||
} );
|
|
||||||
|
|
||||||
if ( changelogEntry === false ) {
|
if ( ! changelogEntry ) {
|
||||||
continue;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
fs.readFile( './plugins/woocommerce/readme.txt', 'utf-8', function( err, data ) {
|
|
||||||
if ( err ) {
|
|
||||||
console.error( err );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
changelogTxt = data.split( "\n" );
|
fs.readFile( './plugins/woocommerce/readme.txt', 'utf-8', function( err, data ) {
|
||||||
let isInRange = false;
|
|
||||||
let newChangelogTxt = [];
|
|
||||||
|
|
||||||
for ( const line of changelogTxt ) {
|
|
||||||
if ( isInRange === false && line === '== Changelog ==' ) {
|
|
||||||
isInRange = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( isInRange === true && line.match( /\*\*WooCommerce Blocks/ ) ) {
|
|
||||||
isInRange = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the first match of the entry "Type".
|
|
||||||
if ( isInRange && line.match( `\\* ${changelogEntryType} -` ) ) {
|
|
||||||
newChangelogTxt.push( '* ' + changelogEntryType + ' - ' + changelogEntry + ` [#${{ needs.prep.outputs.pr }}](https://github.com/woocommerce/woocommerce/pull/${{ needs.prep.outputs.pr }})` );
|
|
||||||
newChangelogTxt.push( line );
|
|
||||||
isInRange = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
newChangelogTxt.push( line );
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.writeFile( './plugins/woocommerce/readme.txt', newChangelogTxt.join( "\n" ), err => {
|
|
||||||
if ( err ) {
|
if ( err ) {
|
||||||
console.error( `Unable to generate the changelog entry for PR ${{ needs.prep.outputs.pr }}` );
|
console.error( err );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
changelogTxt = data.split( "\n" );
|
||||||
|
let isInRange = false;
|
||||||
|
let newChangelogTxt = [];
|
||||||
|
|
||||||
|
for ( const line of changelogTxt ) {
|
||||||
|
if ( isInRange === false && line === '== Changelog ==' ) {
|
||||||
|
isInRange = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isInRange === true && line.match( /\*\*WooCommerce Blocks/ ) ) {
|
||||||
|
isInRange = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the first match of the entry "Type".
|
||||||
|
if ( isInRange && line.match( `\\* ${changelogEntryType} -` ) ) {
|
||||||
|
newChangelogTxt.push( '* ' + changelogEntryType + ' - ' + changelogEntry + ` [#${{ needs.prep.outputs.pr }}](https://github.com/woocommerce/woocommerce/pull/${{ needs.prep.outputs.pr }})` );
|
||||||
|
newChangelogTxt.push( line );
|
||||||
|
isInRange = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
newChangelogTxt.push( line );
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFile( './plugins/woocommerce/readme.txt', newChangelogTxt.join( "\n" ), err => {
|
||||||
|
if ( err ) {
|
||||||
|
console.error( `Unable to generate the changelog entry for PR ${{ needs.prep.outputs.pr }}` );
|
||||||
|
}
|
||||||
|
} );
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ jobs:
|
||||||
|
|
||||||
- name: "If community PR, assign a reviewer"
|
- name: "If community PR, assign a reviewer"
|
||||||
if: github.event.pull_request && steps.check.outputs.is-community == 'yes'
|
if: github.event.pull_request && steps.check.outputs.is-community == 'yes'
|
||||||
uses: shufo/auto-assign-reviewer-by-files@24a9fcbd5c51c4403b64c8b6e087c824b67a5c35
|
uses: shufo/auto-assign-reviewer-by-files@f5f3db9ef06bd72ab6978996988c6462cbdaabf6
|
||||||
with:
|
with:
|
||||||
config: ".github/project-community-pr-assigner.yml"
|
config: ".github/project-community-pr-assigner.yml"
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.PR_ASSIGN_TOKEN }}
|
||||||
|
|
|
@ -1,17 +1,20 @@
|
||||||
name: Run daily tests in an environment with COT enabled
|
name: Run daily tests in an environment with COT enabled
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "30 2 * * *"
|
- cron: '30 2 * * *'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
cot-e2e-tests-run:
|
cot-e2e-tests-run:
|
||||||
name: Runs E2E tests with COT enabled.
|
name: Runs E2E tests with COT enabled.
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
|
env:
|
||||||
|
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/allure-results
|
||||||
|
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/allure-report
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
@ -20,6 +23,8 @@ jobs:
|
||||||
|
|
||||||
- name: Load docker images and start containers with COT enabled.
|
- name: Load docker images and start containers with COT enabled.
|
||||||
working-directory: plugins/woocommerce
|
working-directory: plugins/woocommerce
|
||||||
|
env:
|
||||||
|
ENABLE_HPOS: 1
|
||||||
run: pnpm env:test:cot --filter=woocommerce
|
run: pnpm env:test:cot --filter=woocommerce
|
||||||
|
|
||||||
- name: Download and install Chromium browser.
|
- name: Download and install Chromium browser.
|
||||||
|
@ -30,7 +35,7 @@ jobs:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
id: run_playwright_e2e_tests
|
id: run_playwright_e2e_tests
|
||||||
env:
|
env:
|
||||||
USE_WP_ENV: 1
|
USE_WP_ENV: 1
|
||||||
working-directory: plugins/woocommerce
|
working-directory: plugins/woocommerce
|
||||||
run: pnpm exec playwright test --config=tests/e2e-pw/playwright.config.js
|
run: pnpm exec playwright test --config=tests/e2e-pw/playwright.config.js
|
||||||
|
|
||||||
|
@ -43,7 +48,7 @@ jobs:
|
||||||
steps.run_playwright_e2e_tests.conclusion != 'skipped'
|
steps.run_playwright_e2e_tests.conclusion != 'skipped'
|
||||||
)
|
)
|
||||||
working-directory: plugins/woocommerce
|
working-directory: plugins/woocommerce
|
||||||
run: pnpm exec allure generate --clean e2e/allure-results --output e2e/allure-report
|
run: pnpm exec allure generate --clean ${{ env.ALLURE_RESULTS_DIR }} --output ${{ env.ALLURE_REPORT_DIR }}
|
||||||
|
|
||||||
- name: Archive Playwright E2E test report
|
- name: Archive Playwright E2E test report
|
||||||
if: |
|
if: |
|
||||||
|
@ -53,8 +58,8 @@ jobs:
|
||||||
with:
|
with:
|
||||||
name: e2e-test-report---pr-${{ github.event.number }}
|
name: e2e-test-report---pr-${{ github.event.number }}
|
||||||
path: |
|
path: |
|
||||||
plugins/woocommerce/e2e/allure-results
|
${{ env.ALLURE_RESULTS_DIR }}
|
||||||
plugins/woocommerce/e2e/allure-report
|
${{ env.ALLURE_REPORT_DIR }}
|
||||||
if-no-files-found: ignore
|
if-no-files-found: ignore
|
||||||
retention-days: 5
|
retention-days: 5
|
||||||
|
|
||||||
|
@ -62,7 +67,8 @@ jobs:
|
||||||
name: Runs API tests with COT enabled.
|
name: Runs API tests with COT enabled.
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
env:
|
env:
|
||||||
API_TEST_REPORT_DIR: ${{ github.workspace }}/api-test-report
|
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/api-test-report/allure-results
|
||||||
|
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/api-test-report/allure-report
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
@ -71,6 +77,8 @@ jobs:
|
||||||
|
|
||||||
- name: Load docker images and start containers with COT enabled.
|
- name: Load docker images and start containers with COT enabled.
|
||||||
working-directory: plugins/woocommerce
|
working-directory: plugins/woocommerce
|
||||||
|
env:
|
||||||
|
ENABLE_HPOS: 1
|
||||||
run: pnpm env:test:cot --filter=woocommerce
|
run: pnpm env:test:cot --filter=woocommerce
|
||||||
|
|
||||||
- name: Run Playwright API tests.
|
- name: Run Playwright API tests.
|
||||||
|
@ -81,6 +89,7 @@ jobs:
|
||||||
USER_KEY: admin
|
USER_KEY: admin
|
||||||
USER_SECRET: password
|
USER_SECRET: password
|
||||||
run: pnpm exec playwright test --config=tests/api-core-tests/playwright.config.js
|
run: pnpm exec playwright test --config=tests/api-core-tests/playwright.config.js
|
||||||
|
|
||||||
- name: Generate Playwright API Test report.
|
- name: Generate Playwright API Test report.
|
||||||
id: generate_api_report
|
id: generate_api_report
|
||||||
if: |
|
if: |
|
||||||
|
@ -90,7 +99,8 @@ jobs:
|
||||||
steps.run_playwright_api_tests.conclusion != 'skipped'
|
steps.run_playwright_api_tests.conclusion != 'skipped'
|
||||||
)
|
)
|
||||||
working-directory: plugins/woocommerce
|
working-directory: plugins/woocommerce
|
||||||
run: pnpm exec allure generate --clean api-test-report/allure-results --output api-test-report/allure-report
|
run: pnpm exec allure generate --clean ${{ env.ALLURE_RESULTS_DIR }} --output ${{ env.ALLURE_REPORT_DIR }}
|
||||||
|
|
||||||
- name: Archive Playwright API test report
|
- name: Archive Playwright API test report
|
||||||
if: |
|
if: |
|
||||||
always() &&
|
always() &&
|
||||||
|
@ -99,8 +109,8 @@ jobs:
|
||||||
with:
|
with:
|
||||||
name: api-test-report---pr-${{ github.event.number }}
|
name: api-test-report---pr-${{ github.event.number }}
|
||||||
path: |
|
path: |
|
||||||
plugins/woocommerce/api-test-report/allure-results
|
${{ env.ALLURE_RESULTS_DIR }}
|
||||||
plugins/woocommerce/api-test-report/allure-report
|
${{ env.ALLURE_REPORT_DIR }}
|
||||||
if-no-files-found: ignore
|
if-no-files-found: ignore
|
||||||
retention-days: 5
|
retention-days: 5
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,9 @@ jobs:
|
||||||
name: Runs E2E tests with COT enabled.
|
name: Runs E2E tests with COT enabled.
|
||||||
if: "${{ github.event_name == 'workflow_dispatch' || github.event.label.name == 'focus: custom order tables' }}"
|
if: "${{ github.event_name == 'workflow_dispatch' || github.event.label.name == 'focus: custom order tables' }}"
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
|
env:
|
||||||
|
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/allure-results
|
||||||
|
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/allure-report
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
@ -21,6 +24,8 @@ jobs:
|
||||||
|
|
||||||
- name: Load docker images and start containers with COT enabled.
|
- name: Load docker images and start containers with COT enabled.
|
||||||
working-directory: plugins/woocommerce
|
working-directory: plugins/woocommerce
|
||||||
|
env:
|
||||||
|
ENABLE_HPOS: 1
|
||||||
run: pnpm env:test:cot --filter=woocommerce
|
run: pnpm env:test:cot --filter=woocommerce
|
||||||
|
|
||||||
- name: Download and install Chromium browser.
|
- name: Download and install Chromium browser.
|
||||||
|
@ -31,7 +36,7 @@ jobs:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
id: run_playwright_e2e_tests
|
id: run_playwright_e2e_tests
|
||||||
env:
|
env:
|
||||||
USE_WP_ENV: 1
|
USE_WP_ENV: 1
|
||||||
working-directory: plugins/woocommerce
|
working-directory: plugins/woocommerce
|
||||||
run: pnpm exec playwright test --config=tests/e2e-pw/playwright.config.js
|
run: pnpm exec playwright test --config=tests/e2e-pw/playwright.config.js
|
||||||
|
|
||||||
|
@ -44,7 +49,7 @@ jobs:
|
||||||
steps.run_playwright_e2e_tests.conclusion != 'skipped'
|
steps.run_playwright_e2e_tests.conclusion != 'skipped'
|
||||||
)
|
)
|
||||||
working-directory: plugins/woocommerce
|
working-directory: plugins/woocommerce
|
||||||
run: pnpm exec allure generate --clean e2e/allure-results --output e2e/allure-report
|
run: pnpm exec allure generate --clean ${{ env.ALLURE_RESULTS_DIR }} --output ${{ env.ALLURE_REPORT_DIR }}
|
||||||
|
|
||||||
- name: Archive Playwright E2E test report
|
- name: Archive Playwright E2E test report
|
||||||
if: |
|
if: |
|
||||||
|
@ -54,8 +59,8 @@ jobs:
|
||||||
with:
|
with:
|
||||||
name: e2e-test-report---pr-${{ github.event.number }}
|
name: e2e-test-report---pr-${{ github.event.number }}
|
||||||
path: |
|
path: |
|
||||||
plugins/woocommerce/e2e/allure-results
|
${{ env.ALLURE_RESULTS_DIR }}
|
||||||
plugins/woocommerce/e2e/allure-report
|
${{ env.ALLURE_REPORT_DIR }}
|
||||||
if-no-files-found: ignore
|
if-no-files-found: ignore
|
||||||
retention-days: 5
|
retention-days: 5
|
||||||
|
|
||||||
|
@ -64,7 +69,8 @@ jobs:
|
||||||
if: "${{ github.event_name == 'workflow_dispatch' || github.event.label.name == 'focus: custom order tables' }}"
|
if: "${{ github.event_name == 'workflow_dispatch' || github.event.label.name == 'focus: custom order tables' }}"
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
env:
|
env:
|
||||||
API_TEST_REPORT_DIR: ${{ github.workspace }}/api-test-report
|
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/api-test-report/allure-results
|
||||||
|
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/api-test-report/allure-report
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
@ -73,6 +79,8 @@ jobs:
|
||||||
|
|
||||||
- name: Load docker images and start containers with COT enabled.
|
- name: Load docker images and start containers with COT enabled.
|
||||||
working-directory: plugins/woocommerce
|
working-directory: plugins/woocommerce
|
||||||
|
env:
|
||||||
|
ENABLE_HPOS: 1
|
||||||
run: pnpm env:test:cot --filter=woocommerce
|
run: pnpm env:test:cot --filter=woocommerce
|
||||||
|
|
||||||
- name: Run Playwright API tests.
|
- name: Run Playwright API tests.
|
||||||
|
@ -93,7 +101,7 @@ jobs:
|
||||||
steps.run_playwright_api_tests.conclusion != 'skipped'
|
steps.run_playwright_api_tests.conclusion != 'skipped'
|
||||||
)
|
)
|
||||||
working-directory: plugins/woocommerce
|
working-directory: plugins/woocommerce
|
||||||
run: pnpm exec allure generate --clean api-test-report/allure-results --output api-test-report/allure-report
|
run: pnpm exec allure generate --clean ${{ env.ALLURE_RESULTS_DIR }} --output ${{ env.ALLURE_REPORT_DIR }}
|
||||||
|
|
||||||
- name: Archive Playwright API test report
|
- name: Archive Playwright API test report
|
||||||
if: |
|
if: |
|
||||||
|
@ -103,8 +111,8 @@ jobs:
|
||||||
with:
|
with:
|
||||||
name: api-test-report---pr-${{ github.event.number }}
|
name: api-test-report---pr-${{ github.event.number }}
|
||||||
path: |
|
path: |
|
||||||
plugins/woocommerce/api-test-report/allure-results
|
${{ env.ALLURE_RESULTS_DIR }}
|
||||||
plugins/woocommerce/api-test-report/allure-report
|
${{ env.ALLURE_REPORT_DIR }}
|
||||||
if-no-files-found: ignore
|
if-no-files-found: ignore
|
||||||
retention-days: 5
|
retention-days: 5
|
||||||
|
|
||||||
|
|
|
@ -10,52 +10,112 @@ env:
|
||||||
GIT_AUTHOR_EMAIL: 'no-reply@woocommerce.com'
|
GIT_AUTHOR_EMAIL: 'no-reply@woocommerce.com'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
update-changelog-in-trunk:
|
changelog-version-update:
|
||||||
name: Update changelog in trunk
|
name: Update changelog and version
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Get tag name
|
|
||||||
id: tag
|
|
||||||
uses: actions/github-script@v6
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const tag = ${{ toJSON( github.event.release.tag_name ) }}
|
|
||||||
|
|
||||||
console.log( `::set-output name=tag::release/${ tag.substring( 0, 3 ) }` )
|
|
||||||
|
|
||||||
- name: Git fetch trunk branch
|
- name: Git fetch trunk branch
|
||||||
run: git fetch origin trunk
|
run: git fetch origin trunk
|
||||||
|
|
||||||
- name: Copy changelog.txt to vm root
|
- name: Copy readme.txt to vm root
|
||||||
run: cp changelog.txt ../../changelog.txt
|
run: cp ./plugins/woocommerce/readme.txt ../../readme.txt
|
||||||
|
|
||||||
- name: Switch to trunk branch
|
- name: Switch to trunk branch
|
||||||
run: git checkout trunk
|
run: git checkout trunk
|
||||||
|
|
||||||
- name: Create a new branch based on trunk
|
- name: Create a new branch based on trunk
|
||||||
run: git checkout -b update/changelog-from-release-${{ github.event.release.tag_name }}
|
run: git checkout -b prep/post-release-tasks-${{ github.event.release.tag_name }}
|
||||||
|
|
||||||
- name: Copy saved changelog.txt to monorepo
|
- name: Check if we need to continue processing
|
||||||
run: cp ../../changelog.txt ./changelog.txt
|
uses: actions/github-script@v6
|
||||||
|
id: check
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const fs = require( 'node:fs' );
|
||||||
|
const version = ${{ toJSON( github.event.release.tag_name ) }}
|
||||||
|
|
||||||
|
fs.readFile( './plugins/woocommerce/readme.txt', 'utf-8', function( err, data ) {
|
||||||
|
if ( err ) {
|
||||||
|
console.error( err );
|
||||||
|
}
|
||||||
|
|
||||||
|
const regex = /Stable\stag:\s(\d+\.\d+\.\d+)/;
|
||||||
|
|
||||||
|
const stableVersion = data.match( regex )[1];
|
||||||
|
|
||||||
|
// If the release version is less than stable version we can bail.
|
||||||
|
if ( version.localeCompare( stableVersion, undefined, { numeric: true, sensitivity: 'base' } ) == -1 ) {
|
||||||
|
console.log( 'Release version is less than stable version. No automated action taken. A manual process is required.' );
|
||||||
|
console.log( `::set-output name=continue::false` )
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
console.log( `::set-output name=continue::true` )
|
||||||
|
}
|
||||||
|
} )
|
||||||
|
|
||||||
|
- name: Update changelog.txt entries
|
||||||
|
uses: actions/github-script@v6
|
||||||
|
id: update-entries
|
||||||
|
if: steps.check.outputs.continue == 'true'
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const fs = require( 'node:fs' );
|
||||||
|
const version = ${{ toJSON( github.event.release.tag_name ) }}
|
||||||
|
|
||||||
|
// Read the saved readme.txt file from earlier.
|
||||||
|
fs.readFile( '../../readme.txt', 'utf-8', function( err, readme ) {
|
||||||
|
if ( err ) {
|
||||||
|
console.log( `::set-output name=continue::false` )
|
||||||
|
console.error( err );
|
||||||
|
}
|
||||||
|
|
||||||
|
const regex = /(== Changelog ==[\s\S]+)\s{2}\[See changelog for all versions\]\(https:\/\/raw\.githubusercontent\.com\/woocommerce\/woocommerce\/trunk\/changelog\.txt\)\./;
|
||||||
|
|
||||||
|
const entries = readme.match( regex )[1];
|
||||||
|
|
||||||
|
fs.readFile( './changelog.txt', 'utf-8', function( err, changelog ) {
|
||||||
|
if ( err ) {
|
||||||
|
console.log( `::set-output name=continue::false` )
|
||||||
|
console.error( err );
|
||||||
|
}
|
||||||
|
|
||||||
|
const regex = /== Changelog ==/;
|
||||||
|
|
||||||
|
const updatedChangelog = changelog.replace( regex, entries );
|
||||||
|
|
||||||
|
fs.writeFile( './changelog.txt', updatedChangelog, err => {
|
||||||
|
if ( err ) {
|
||||||
|
console.log( `::set-output name=continue::false` )
|
||||||
|
console.error( 'Unable to update changelog entries in changelog.txt' );
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log( `::set-output name=continue::true` )
|
||||||
|
} )
|
||||||
|
} )
|
||||||
|
} )
|
||||||
|
|
||||||
- name: Commit changes
|
- name: Commit changes
|
||||||
run: git commit -am "Update changelog.txt from release ${{ github.event.release.tag_name }}"
|
if: steps.update-entries.outputs.continue == 'true'
|
||||||
|
run: git commit -am "Prep trunk post release ${{ github.event.release.tag_name }}"
|
||||||
|
|
||||||
- name: Push branch up
|
- name: Push branch up
|
||||||
run: git push origin update/changelog-from-release-${{ github.event.release.tag_name }}
|
if: steps.update-entries.outputs.continue == 'true'
|
||||||
|
run: git push origin prep/post-release-tasks-${{ github.event.release.tag_name }}
|
||||||
|
|
||||||
- name: Create the PR
|
- name: Create the PR
|
||||||
|
if: steps.update-entries.outputs.continue == 'true'
|
||||||
uses: actions/github-script@v6
|
uses: actions/github-script@v6
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const body = "This PR updates the changelog.txt based on the latest release: ${{ github.event.release.tag_name }}"
|
const body = "This PR updates the changelog.txt entries based on the latest release: ${{ github.event.release.tag_name }}"
|
||||||
|
|
||||||
const pr = await github.rest.pulls.create({
|
const pr = await github.rest.pulls.create({
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
repo: context.repo.repo,
|
repo: context.repo.repo,
|
||||||
title: "Update changelog.txt from release ${{ github.event.release.tag_name }}",
|
title: "Update changelog.txt from release ${{ github.event.release.tag_name }}",
|
||||||
head: "update/changelog-from-release-${{ github.event.release.tag_name }}",
|
head: "prep/post-release-tasks-${{ github.event.release.tag_name }}",
|
||||||
base: "trunk",
|
base: "trunk",
|
||||||
body: body
|
body: body
|
||||||
})
|
})
|
||||||
|
|
|
@ -11,6 +11,9 @@ jobs:
|
||||||
e2e-tests-run:
|
e2e-tests-run:
|
||||||
name: Runs E2E tests.
|
name: Runs E2E tests.
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
|
env:
|
||||||
|
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/allure-results
|
||||||
|
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/allure-report
|
||||||
outputs:
|
outputs:
|
||||||
E2E_GRAND_TOTAL: ${{ steps.count_e2e_total.outputs.E2E_GRAND_TOTAL }}
|
E2E_GRAND_TOTAL: ${{ steps.count_e2e_total.outputs.E2E_GRAND_TOTAL }}
|
||||||
steps:
|
steps:
|
||||||
|
@ -21,6 +24,8 @@ jobs:
|
||||||
|
|
||||||
- name: Load docker images and start containers.
|
- name: Load docker images and start containers.
|
||||||
working-directory: plugins/woocommerce
|
working-directory: plugins/woocommerce
|
||||||
|
env:
|
||||||
|
ENABLE_HPOS: 0
|
||||||
run: pnpm env:test --filter=woocommerce
|
run: pnpm env:test --filter=woocommerce
|
||||||
|
|
||||||
- name: Download and install Chromium browser.
|
- name: Download and install Chromium browser.
|
||||||
|
@ -40,9 +45,9 @@ jobs:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
id: run_playwright_e2e_tests
|
id: run_playwright_e2e_tests
|
||||||
env:
|
env:
|
||||||
USE_WP_ENV: 1
|
USE_WP_ENV: 1
|
||||||
E2E_MAX_FAILURES: 15
|
E2E_MAX_FAILURES: 15
|
||||||
FORCE_COLOR: 1
|
FORCE_COLOR: 1
|
||||||
working-directory: plugins/woocommerce
|
working-directory: plugins/woocommerce
|
||||||
run: pnpm exec playwright test --config=tests/e2e-pw/playwright.config.js
|
run: pnpm exec playwright test --config=tests/e2e-pw/playwright.config.js
|
||||||
|
|
||||||
|
@ -55,7 +60,7 @@ jobs:
|
||||||
steps.run_playwright_e2e_tests.conclusion != 'skipped'
|
steps.run_playwright_e2e_tests.conclusion != 'skipped'
|
||||||
)
|
)
|
||||||
working-directory: plugins/woocommerce
|
working-directory: plugins/woocommerce
|
||||||
run: pnpm exec allure generate --clean e2e/allure-results --output e2e/allure-report
|
run: pnpm exec allure generate --clean ${{ env.ALLURE_RESULTS_DIR }} --output ${{ env.ALLURE_REPORT_DIR }}
|
||||||
|
|
||||||
- name: Archive Playwright E2E test report
|
- name: Archive Playwright E2E test report
|
||||||
if: |
|
if: |
|
||||||
|
@ -65,8 +70,8 @@ jobs:
|
||||||
with:
|
with:
|
||||||
name: e2e-test-report---pr-${{ github.event.number }}
|
name: e2e-test-report---pr-${{ github.event.number }}
|
||||||
path: |
|
path: |
|
||||||
plugins/woocommerce/e2e/allure-results
|
${{ env.ALLURE_RESULTS_DIR }}
|
||||||
plugins/woocommerce/e2e/allure-report
|
${{ env.ALLURE_REPORT_DIR }}
|
||||||
if-no-files-found: ignore
|
if-no-files-found: ignore
|
||||||
retention-days: 5
|
retention-days: 5
|
||||||
|
|
||||||
|
@ -74,7 +79,8 @@ jobs:
|
||||||
name: Runs API tests.
|
name: Runs API tests.
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
env:
|
env:
|
||||||
API_TEST_REPORT_DIR: ${{ github.workspace }}/api-test-report
|
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/api-test-report/allure-results
|
||||||
|
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/api-test-report/allure-report
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
@ -83,6 +89,8 @@ jobs:
|
||||||
|
|
||||||
- name: Load docker images and start containers.
|
- name: Load docker images and start containers.
|
||||||
working-directory: plugins/woocommerce
|
working-directory: plugins/woocommerce
|
||||||
|
env:
|
||||||
|
ENABLE_HPOS: 0
|
||||||
run: pnpm env:test --filter=woocommerce
|
run: pnpm env:test --filter=woocommerce
|
||||||
|
|
||||||
- name: Run Playwright API tests.
|
- name: Run Playwright API tests.
|
||||||
|
@ -103,7 +111,7 @@ jobs:
|
||||||
steps.run_playwright_api_tests.conclusion != 'skipped'
|
steps.run_playwright_api_tests.conclusion != 'skipped'
|
||||||
)
|
)
|
||||||
working-directory: plugins/woocommerce
|
working-directory: plugins/woocommerce
|
||||||
run: pnpm exec allure generate --clean api-test-report/allure-results --output api-test-report/allure-report
|
run: pnpm exec allure generate --clean ${{ env.ALLURE_RESULTS_DIR }} --output ${{ env.ALLURE_REPORT_DIR }}
|
||||||
- name: Archive Playwright API test report
|
- name: Archive Playwright API test report
|
||||||
if: |
|
if: |
|
||||||
always() &&
|
always() &&
|
||||||
|
@ -112,8 +120,8 @@ jobs:
|
||||||
with:
|
with:
|
||||||
name: api-test-report---pr-${{ github.event.number }}
|
name: api-test-report---pr-${{ github.event.number }}
|
||||||
path: |
|
path: |
|
||||||
plugins/woocommerce/api-test-report/allure-results
|
${{ env.ALLURE_RESULTS_DIR }}
|
||||||
plugins/woocommerce/api-test-report/allure-report
|
${{ env.ALLURE_REPORT_DIR }}
|
||||||
if-no-files-found: ignore
|
if-no-files-found: ignore
|
||||||
retention-days: 5
|
retention-days: 5
|
||||||
|
|
||||||
|
@ -128,6 +136,8 @@ jobs:
|
||||||
|
|
||||||
- name: Load docker images and start containers.
|
- name: Load docker images and start containers.
|
||||||
working-directory: plugins/woocommerce
|
working-directory: plugins/woocommerce
|
||||||
|
env:
|
||||||
|
ENABLE_HPOS: 0
|
||||||
run: |
|
run: |
|
||||||
pnpm env:dev --filter=woocommerce
|
pnpm env:dev --filter=woocommerce
|
||||||
pnpm env:performance-init --filter=woocommerce
|
pnpm env:performance-init --filter=woocommerce
|
||||||
|
|
|
@ -1,35 +1,46 @@
|
||||||
name: Run code sniff on PR
|
name: Run code sniff on PR
|
||||||
on:
|
on: pull_request
|
||||||
pull_request
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
env:
|
||||||
|
PHPCS: ./plugins/woocommerce/vendor/bin/phpcs # Run WooCommerce phpcs setup in phpcs-changed instead of default
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
name: Code sniff (PHP 7.4, WP Latest)
|
name: Code sniff (PHP 7.4, WP Latest)
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup WooCommerce Monorepo
|
- name: Get Changed Files
|
||||||
uses: ./.github/actions/setup-woocommerce-monorepo
|
id: changed-files
|
||||||
with:
|
uses: tj-actions/changed-files@v32
|
||||||
build: false
|
with:
|
||||||
|
files: |
|
||||||
|
**/*.php
|
||||||
|
|
||||||
- name: Tool versions
|
- name: Setup WooCommerce Monorepo
|
||||||
run: |
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
php --version
|
uses: ./.github/actions/setup-woocommerce-monorepo
|
||||||
composer --version
|
with:
|
||||||
|
build: false
|
||||||
|
|
||||||
- name: Run code sniffer
|
- name: Tool versions
|
||||||
uses: thenabeel/action-phpcs@v8
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
with:
|
run: |
|
||||||
files: "**.php"
|
php --version
|
||||||
phpcs_path: plugins/woocommerce/vendor/bin/phpcs
|
composer --version
|
||||||
standard: phpcs.xml
|
phpcs-changed --version
|
||||||
|
|
||||||
|
- name: Run PHPCS
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
|
run: |
|
||||||
|
HEAD_REF=$(git rev-parse HEAD)
|
||||||
|
git checkout $HEAD_REF
|
||||||
|
phpcs-changed --git --git-base ${{ github.base_ref }} ${{ steps.changed-files.outputs.all_changed_files }}
|
||||||
|
|
|
@ -18,8 +18,9 @@ jobs:
|
||||||
id: run
|
id: run
|
||||||
working-directory: tools/code-analyzer
|
working-directory: tools/code-analyzer
|
||||||
run: |
|
run: |
|
||||||
version=$(pnpm run analyzer major-minor "${{ github.head_ref || github.ref_name }}" "plugins/woocommerce/woocommerce.php" | tail -n 1)
|
HEAD_REF=$(git rev-parse HEAD)
|
||||||
pnpm run analyzer "$GITHUB_HEAD_REF" $version -o "github"
|
version=$(pnpm run analyzer major-minor "$HEAD_REF" "plugins/woocommerce/woocommerce.php" | tail -n 1)
|
||||||
|
pnpm run analyzer "$HEAD_REF" $version -o "github"
|
||||||
- name: Print results
|
- name: Print results
|
||||||
id: results
|
id: results
|
||||||
run: echo "::set-output name=results::${{ steps.run.outputs.templates }}${{ steps.run.outputs.wphooks }}${{ steps.run.outputs.schema }}${{ steps.run.outputs.database }}"
|
run: echo "::set-output name=results::${{ steps.run.outputs.templates }}${{ steps.run.outputs.wphooks }}${{ steps.run.outputs.schema }}${{ steps.run.outputs.database }}"
|
||||||
|
|
|
@ -37,8 +37,3 @@ jobs:
|
||||||
env:
|
env:
|
||||||
PULL_REQUEST_ID: ${{ github.event.pull_request.node_id }}
|
PULL_REQUEST_ID: ${{ github.event.pull_request.node_id }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: "Run the script to post a comment with next steps hint"
|
|
||||||
run: php add-post-merge-comment.php
|
|
||||||
env:
|
|
||||||
PULL_REQUEST_ID: ${{ github.event.pull_request.node_id }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
|
@ -0,0 +1,154 @@
|
||||||
|
/**
|
||||||
|
* Script to generate the test results summary.
|
||||||
|
*/
|
||||||
|
const { API_SUMMARY_PATH, E2E_PW_SUMMARY_PATH } = process.env;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the given `duration` from milliseconds to a more user-friendly string.
|
||||||
|
* For example, if `duration = 323000`, this function would return `5m 23s`.
|
||||||
|
*
|
||||||
|
* @param {Number} duration Duration in millisecods, as read from either the `summary.json` file in the Allure report, or from the `test-results.json` file from the Jest-Puppeteer report.
|
||||||
|
* @returns String in "5m 23s" format.
|
||||||
|
*/
|
||||||
|
const getFormattedDuration = ( duration ) => {
|
||||||
|
const durationMinutes = Math.floor( duration / 1000 / 60 );
|
||||||
|
const durationSeconds = Math.floor( ( duration / 1000 ) % 60 );
|
||||||
|
return `${ durationMinutes }m ${ durationSeconds }s`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the test report statistics (the number of tests that passed, failed, skipped, etc.) from Allure report's `summary.json` file.
|
||||||
|
*
|
||||||
|
* @param {string} summaryJSONPath Path to the Allure report's `summary.json` file.
|
||||||
|
* @returns An object containing relevant statistics from the Allure report.
|
||||||
|
*/
|
||||||
|
const getAllureSummaryStats = ( summaryJSONPath ) => {
|
||||||
|
const summary = require( summaryJSONPath );
|
||||||
|
const { statistic, time } = summary;
|
||||||
|
const { passed, failed, skipped, broken, unknown, total } = statistic;
|
||||||
|
const { duration } = time;
|
||||||
|
|
||||||
|
return {
|
||||||
|
passed,
|
||||||
|
failed,
|
||||||
|
skipped,
|
||||||
|
broken,
|
||||||
|
unknown,
|
||||||
|
total,
|
||||||
|
duration,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct the array to be used for the API table row.
|
||||||
|
*
|
||||||
|
* @returns Array of API test result stats.
|
||||||
|
*/
|
||||||
|
const createAPITableRow = () => {
|
||||||
|
const { passed, failed, skipped, broken, unknown, total, duration } =
|
||||||
|
getAllureSummaryStats( API_SUMMARY_PATH );
|
||||||
|
const durationFormatted = getFormattedDuration( duration );
|
||||||
|
|
||||||
|
return [
|
||||||
|
'API Tests',
|
||||||
|
passed.toString(),
|
||||||
|
failed.toString(),
|
||||||
|
broken.toString(),
|
||||||
|
skipped.toString(),
|
||||||
|
unknown.toString(),
|
||||||
|
total.toString(),
|
||||||
|
durationFormatted,
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct the array to be used for the E2E table row.
|
||||||
|
*
|
||||||
|
* @returns Array of E2E test result stats.
|
||||||
|
*/
|
||||||
|
const createE2ETableRow = () => {
|
||||||
|
const { passed, failed, skipped, broken, unknown, total, duration } =
|
||||||
|
getAllureSummaryStats( E2E_PW_SUMMARY_PATH );
|
||||||
|
const durationFormatted = getFormattedDuration( duration );
|
||||||
|
|
||||||
|
return [
|
||||||
|
'E2E Tests',
|
||||||
|
passed.toString(),
|
||||||
|
failed.toString(),
|
||||||
|
broken.toString(),
|
||||||
|
skipped.toString(),
|
||||||
|
unknown.toString(),
|
||||||
|
total.toString(),
|
||||||
|
durationFormatted,
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the heading and test results table.
|
||||||
|
*
|
||||||
|
* @param core The GitHub Actions toolkit core object
|
||||||
|
*/
|
||||||
|
const addSummaryHeadingAndTable = ( core ) => {
|
||||||
|
const apiTableRow = createAPITableRow();
|
||||||
|
const e2eTableRow = createE2ETableRow();
|
||||||
|
|
||||||
|
core.summary.addHeading( 'Smoke tests on trunk' ).addTable( [
|
||||||
|
[
|
||||||
|
{ data: 'Test :test_tube:', header: true },
|
||||||
|
{ data: 'Passed :white_check_mark:', header: true },
|
||||||
|
{ data: 'Failed :rotating_light:', header: true },
|
||||||
|
{ data: 'Broken :construction:', header: true },
|
||||||
|
{ data: 'Skipped :next_track_button:', header: true },
|
||||||
|
{ data: 'Unknown :grey_question:', header: true },
|
||||||
|
{ data: 'Total :bar_chart:', header: true },
|
||||||
|
{ data: 'Duration :stopwatch:', header: true },
|
||||||
|
],
|
||||||
|
apiTableRow,
|
||||||
|
e2eTableRow,
|
||||||
|
] );
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the summary footer.
|
||||||
|
*
|
||||||
|
* @param core The GitHub Actions toolkit core object
|
||||||
|
*/
|
||||||
|
const addSummaryFooter = ( core ) => {
|
||||||
|
core.summary
|
||||||
|
.addSeparator()
|
||||||
|
.addRaw( 'To view the full API test report, click ' )
|
||||||
|
.addLink(
|
||||||
|
'here.',
|
||||||
|
'https://woocommerce.github.io/woocommerce-test-reports/daily/api'
|
||||||
|
)
|
||||||
|
.addBreak()
|
||||||
|
.addRaw( 'To view the full E2E test report, click ' )
|
||||||
|
.addLink(
|
||||||
|
'here.',
|
||||||
|
'https://woocommerce.github.io/woocommerce-test-reports/daily/e2e'
|
||||||
|
)
|
||||||
|
.addBreak()
|
||||||
|
.addRaw( 'To view all test reports, visit the ' )
|
||||||
|
.addLink(
|
||||||
|
'WooCommerce Test Reports Dashboard.',
|
||||||
|
'https://woocommerce.github.io/woocommerce-test-reports/'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the contents of the test results summary and post it on the workflow run.
|
||||||
|
*
|
||||||
|
* @param {*} params Objects passed from the calling GitHub Action workflow.
|
||||||
|
* @returns Stringified content of the test results summary.
|
||||||
|
*/
|
||||||
|
module.exports = async ( { core } ) => {
|
||||||
|
addSummaryHeadingAndTable( core );
|
||||||
|
|
||||||
|
addSummaryFooter( core );
|
||||||
|
|
||||||
|
const summary = core.summary.stringify();
|
||||||
|
|
||||||
|
await core.summary.write();
|
||||||
|
|
||||||
|
return summary;
|
||||||
|
};
|
|
@ -1,99 +1,175 @@
|
||||||
name: Smoke test daily
|
name: Smoke test daily
|
||||||
on:
|
on:
|
||||||
schedule:
|
# schedule:
|
||||||
- cron: '25 3 * * *'
|
# - cron: '25 3 * * *'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
API_ARTIFACT: api-daily--run-${{ github.run_number }}
|
||||||
|
E2E_ARTIFACT: e2e-daily--run-${{ github.run_number }}
|
||||||
|
FORCE_COLOR: 1
|
||||||
|
BRANCH_NAME: ${{ github.ref_name }}
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
login-run:
|
e2e-tests:
|
||||||
name: Daily smoke test on trunk.
|
name: E2E tests on trunk
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
|
if: always()
|
||||||
env:
|
env:
|
||||||
API_TEST_REPORT_DIR: ${{ github.workspace }}/api-test-report
|
BASE_URL: ${{ secrets.SMOKE_TEST_URL }}
|
||||||
outputs:
|
ADMIN_USER: ${{ secrets.SMOKE_TEST_ADMIN_USER }}
|
||||||
commit_message: ${{ steps.get_commit_message.outputs.commit_message }}
|
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:
|
with:
|
||||||
ref: trunk
|
ref: ${{ env.BRANCH_NAME }}
|
||||||
|
|
||||||
- name: Setup WooCommerce Monorepo
|
- name: Setup WooCommerce Monorepo
|
||||||
uses: ./.github/actions/setup-woocommerce-monorepo
|
uses: ./.github/actions/setup-woocommerce-monorepo
|
||||||
|
with:
|
||||||
|
install-filters: woocommerce
|
||||||
|
build: false
|
||||||
|
|
||||||
- name: Install Jest
|
- name: Download and install Chromium browser.
|
||||||
run: npm install -g jest
|
|
||||||
|
|
||||||
- name: Get latest commit message
|
|
||||||
id: get_commit_message
|
|
||||||
run: |
|
|
||||||
COMMIT_MESSAGE=$(git log --pretty=format:%s -1)
|
|
||||||
echo "::set-output name=commit_message::$COMMIT_MESSAGE"
|
|
||||||
|
|
||||||
- name: Run E2E smoke test.
|
|
||||||
working-directory: plugins/woocommerce
|
working-directory: plugins/woocommerce
|
||||||
env:
|
run: pnpm exec playwright install chromium
|
||||||
SMOKE_TEST_URL: ${{ secrets.SMOKE_TEST_URL }}
|
|
||||||
SMOKE_TEST_ADMIN_USER: ${{ secrets.SMOKE_TEST_ADMIN_USER }}
|
|
||||||
SMOKE_TEST_ADMIN_PASSWORD: ${{ secrets.SMOKE_TEST_ADMIN_PASSWORD }}
|
|
||||||
SMOKE_TEST_ADMIN_USER_EMAIL: ${{ secrets.SMOKE_TEST_ADMIN_USER_EMAIL }}
|
|
||||||
SMOKE_TEST_CUSTOMER_USER: ${{ secrets.SMOKE_TEST_CUSTOMER_USER }}
|
|
||||||
SMOKE_TEST_CUSTOMER_PASSWORD: ${{ secrets.SMOKE_TEST_CUSTOMER_PASSWORD }}
|
|
||||||
WC_E2E_SCREENSHOTS: 1
|
|
||||||
E2E_RETEST: 1
|
|
||||||
E2E_SLACK_TOKEN: ${{ secrets.SMOKE_TEST_SLACK_TOKEN }}
|
|
||||||
E2E_SLACK_CHANNEL: ${{ secrets.SMOKE_TEST_SLACK_CHANNEL }}
|
|
||||||
UPDATE_WC: 1
|
|
||||||
DEFAULT_TIMEOUT_OVERRIDE: 120000
|
|
||||||
run: |
|
|
||||||
pnpm exec wc-e2e docker:up
|
|
||||||
pnpm exec wc-e2e test:e2e tests/e2e/specs/smoke-tests/update-woocommerce.js
|
|
||||||
pnpm exec wc-e2e test:e2e
|
|
||||||
|
|
||||||
- name: Run API smoke tests
|
- name: Run 'Update WooCommerce' test.
|
||||||
|
working-directory: plugins/woocommerce
|
||||||
|
id: e2e-update
|
||||||
|
env:
|
||||||
|
UPDATE_WC: true
|
||||||
|
run: pnpm exec playwright test --config=tests/e2e-pw/playwright.config.js update-woocommerce.spec.js
|
||||||
|
|
||||||
|
- name: Run the rest of E2E tests.
|
||||||
|
timeout-minutes: 60
|
||||||
|
working-directory: plugins/woocommerce
|
||||||
|
id: e2e
|
||||||
|
env:
|
||||||
|
E2E_MAX_FAILURES: 15
|
||||||
|
run: pnpm exec playwright test --config=tests/e2e-pw/playwright.config.js basic.spec.js
|
||||||
|
|
||||||
|
- name: Generate Playwright E2E Test report.
|
||||||
|
id: generate_e2e_report
|
||||||
|
if: |
|
||||||
|
always() &&
|
||||||
|
(
|
||||||
|
steps.e2e-update.conclusion != 'cancelled' ||
|
||||||
|
steps.e2e-update.conclusion != 'skipped' ||
|
||||||
|
steps.e2e.conclusion != 'cancelled' ||
|
||||||
|
steps.e2e.conclusion != 'skipped'
|
||||||
|
)
|
||||||
|
working-directory: plugins/woocommerce
|
||||||
|
run: pnpm exec allure generate --clean ${{ env.ALLURE_RESULTS_DIR }} --output ${{ env.ALLURE_REPORT_DIR }}
|
||||||
|
|
||||||
|
- name: Archive E2E test report
|
||||||
|
if: |
|
||||||
|
always() &&
|
||||||
|
steps.generate_e2e_report.conclusion == 'success'
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: ${{ env.E2E_ARTIFACT }}
|
||||||
|
path: |
|
||||||
|
${{ env.ALLURE_RESULTS_DIR }}
|
||||||
|
${{ env.ALLURE_REPORT_DIR }}
|
||||||
|
if-no-files-found: ignore
|
||||||
|
retention-days: 5
|
||||||
|
|
||||||
|
api-tests:
|
||||||
|
name: API tests on trunk
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
needs: [e2e-tests]
|
||||||
|
if: always()
|
||||||
|
env:
|
||||||
|
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/api-test-report/allure-results
|
||||||
|
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/api-test-report/allure-report
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: ${{ env.BRANCH_NAME }}
|
||||||
|
|
||||||
|
- name: Setup WooCommerce Monorepo
|
||||||
|
uses: ./.github/actions/setup-woocommerce-monorepo
|
||||||
|
with:
|
||||||
|
install-filters: woocommerce
|
||||||
|
build: false
|
||||||
|
|
||||||
|
- name: Run API tests.
|
||||||
if: always()
|
if: always()
|
||||||
id: run_api_tests
|
id: run_playwright_api_tests
|
||||||
working-directory: plugins/woocommerce
|
working-directory: plugins/woocommerce
|
||||||
env:
|
env:
|
||||||
BASE_URL: ${{ secrets.SMOKE_TEST_URL }}
|
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
|
DEFAULT_TIMEOUT_OVERRIDE: 120000
|
||||||
run: pnpm exec wc-api-tests test api
|
run: pnpm exec playwright test --config=tests/api-core-tests/playwright.config.js hello.test.js
|
||||||
|
|
||||||
|
- name: Generate API Test report.
|
||||||
|
id: generate_api_report
|
||||||
|
if: |
|
||||||
|
always() &&
|
||||||
|
(
|
||||||
|
steps.run_playwright_api_tests.conclusion != 'cancelled' ||
|
||||||
|
steps.run_playwright_api_tests.conclusion != 'skipped'
|
||||||
|
)
|
||||||
|
working-directory: plugins/woocommerce
|
||||||
|
run: pnpm exec allure generate --clean ${{ env.ALLURE_RESULTS_DIR }} --output ${{ env.ALLURE_REPORT_DIR }}
|
||||||
|
|
||||||
- name: Archive API test report
|
- name: Archive API test report
|
||||||
if: |
|
if: |
|
||||||
always() &&
|
always() &&
|
||||||
(
|
steps.generate_api_report.conclusion == 'success'
|
||||||
steps.run_api_tests.conclusion != 'cancelled' ||
|
|
||||||
steps.run_api_tests.conclusion != 'skipped'
|
|
||||||
)
|
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: api-test-report---daily
|
name: ${{ env.API_ARTIFACT }}
|
||||||
path: |
|
path: |
|
||||||
${{ env.API_TEST_REPORT_DIR }}/allure-results
|
${{ env.ALLURE_RESULTS_DIR }}
|
||||||
${{ env.API_TEST_REPORT_DIR }}/allure-report
|
${{ env.ALLURE_REPORT_DIR }}
|
||||||
|
if-no-files-found: ignore
|
||||||
retention-days: 5
|
retention-days: 5
|
||||||
|
|
||||||
|
k6-tests:
|
||||||
|
name: k6 tests on trunk
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
needs: [api-tests]
|
||||||
|
if: always()
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: ${{ env.BRANCH_NAME }}
|
||||||
|
|
||||||
|
- name: Setup WooCommerce Monorepo
|
||||||
|
uses: ./.github/actions/setup-woocommerce-monorepo
|
||||||
|
with:
|
||||||
|
install-filters: woocommerce
|
||||||
|
build: false
|
||||||
|
|
||||||
|
- name: Download and install Chromium browser.
|
||||||
|
working-directory: plugins/woocommerce
|
||||||
|
run: pnpm exec playwright install chromium
|
||||||
|
|
||||||
- name: Update performance test site with E2E test
|
- name: Update performance test site with E2E test
|
||||||
if: always()
|
if: always()
|
||||||
working-directory: plugins/woocommerce
|
working-directory: plugins/woocommerce
|
||||||
env:
|
env:
|
||||||
SMOKE_TEST_URL: ${{ secrets.SMOKE_TEST_PERF_URL }}/
|
BASE_URL: ${{ secrets.SMOKE_TEST_PERF_URL }}/
|
||||||
SMOKE_TEST_ADMIN_USER: ${{ secrets.SMOKE_TEST_PERF_ADMIN_USER }}
|
ADMIN_USER: ${{ secrets.SMOKE_TEST_PERF_ADMIN_USER }}
|
||||||
SMOKE_TEST_ADMIN_PASSWORD: ${{ secrets.SMOKE_TEST_PERF_ADMIN_PASSWORD }}
|
ADMIN_PASSWORD: ${{ secrets.SMOKE_TEST_PERF_ADMIN_PASSWORD }}
|
||||||
SMOKE_TEST_ADMIN_USER_EMAIL: ${{ secrets.SMOKE_TEST_ADMIN_USER_EMAIL }}
|
CUSTOMER_USER: ${{ secrets.SMOKE_TEST_PERF_ADMIN_USER }}
|
||||||
SMOKE_TEST_CUSTOMER_USER: ${{ secrets.SMOKE_TEST_CUSTOMER_USER }}
|
CUSTOMER_PASSWORD: ${{ secrets.SMOKE_TEST_PERF_ADMIN_PASSWORD }}
|
||||||
SMOKE_TEST_CUSTOMER_PASSWORD: ${{ secrets.SMOKE_TEST_CUSTOMER_PASSWORD }}
|
UPDATE_WC: true
|
||||||
WC_E2E_SCREENSHOTS: 1
|
DEFAULT_TIMEOUT_OVERRIDE: 120000
|
||||||
E2E_RETEST: 1
|
|
||||||
E2E_RETRY_TIMES: 0
|
|
||||||
E2E_SLACK_TOKEN: ${{ secrets.SMOKE_TEST_SLACK_TOKEN }}
|
|
||||||
E2E_SLACK_CHANNEL: ${{ secrets.SMOKE_TEST_SLACK_CHANNEL }}
|
|
||||||
UPDATE_WC: 1
|
|
||||||
DEFAULT_TIMEOUT_OVERRIDE: 120000
|
|
||||||
run: |
|
run: |
|
||||||
pnpm exec wc-e2e test:e2e tests/e2e/specs/smoke-tests/update-woocommerce.js
|
pnpm exec playwright test --config=tests/e2e-pw/playwright.config.js update-woocommerce.spec.js
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
|
||||||
- name: Install k6
|
- name: Install k6
|
||||||
|
@ -114,34 +190,15 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
./k6 run plugins/woocommerce/tests/performance/tests/gh-action-daily-ext-requests.js
|
./k6 run plugins/woocommerce/tests/performance/tests/gh-action-daily-ext-requests.js
|
||||||
|
|
||||||
build:
|
|
||||||
name: Build zip for PR
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Setup WooCommerce Monorepo
|
|
||||||
uses: ./.github/actions/setup-woocommerce-monorepo
|
|
||||||
with:
|
|
||||||
build: false
|
|
||||||
|
|
||||||
- name: Build zip
|
|
||||||
working-directory: plugins/woocommerce
|
|
||||||
run: bash bin/build-zip.sh
|
|
||||||
|
|
||||||
- name: Upload the zip file as an artifact
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
name: woocommerce
|
|
||||||
path: plugins/woocommerce/woocommerce.zip
|
|
||||||
retention-days: 7
|
|
||||||
|
|
||||||
test-plugins:
|
test-plugins:
|
||||||
name: Smoke tests with ${{ matrix.plugin }} plugin installed
|
name: Smoke tests with ${{ matrix.plugin }} plugin installed
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
needs: [build]
|
needs: [k6-tests]
|
||||||
|
if: always()
|
||||||
|
env:
|
||||||
|
USE_WP_ENV: 1
|
||||||
|
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/allure-results
|
||||||
|
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/allure-report
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
@ -160,59 +217,164 @@ jobs:
|
||||||
- plugin: 'Contact Form 7'
|
- plugin: 'Contact Form 7'
|
||||||
repo: 'takayukister/contact-form-7'
|
repo: 'takayukister/contact-form-7'
|
||||||
steps:
|
steps:
|
||||||
- name: Create dirs.
|
|
||||||
run: |
|
|
||||||
mkdir -p package/woocommerce
|
|
||||||
mkdir -p tmp/woocommerce
|
|
||||||
|
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
path: package/woocommerce
|
ref: ${{ env.BRANCH_NAME }}
|
||||||
|
|
||||||
- name: Download WooCommerce ZIP.
|
- name: Setup WooCommerce Monorepo
|
||||||
uses: actions/download-artifact@v3
|
uses: ./.github/actions/setup-woocommerce-monorepo
|
||||||
with:
|
|
||||||
name: woocommerce
|
|
||||||
path: tmp
|
|
||||||
|
|
||||||
- name: Extract and replace WooCommerce zip.
|
- name: Launch wp-env e2e environment
|
||||||
working-directory: tmp
|
working-directory: plugins/woocommerce
|
||||||
run: |
|
run: pnpm env:test --filter=woocommerce
|
||||||
unzip woocommerce.zip -d .
|
|
||||||
rsync -a woocommerce/* ../package/woocommerce/plugins/woocommerce/
|
|
||||||
|
|
||||||
- name: Load docker images and start containers.
|
- name: Download and install Chromium browser.
|
||||||
working-directory: package/woocommerce
|
working-directory: plugins/woocommerce
|
||||||
run: pnpm docker:up --filter=woocommerce
|
run: pnpm exec playwright install chromium
|
||||||
|
|
||||||
- name: Run tests command.
|
- name: Run 'Upload plugin' test
|
||||||
working-directory: package/woocommerce/plugins/woocommerce
|
id: e2e-upload
|
||||||
|
working-directory: plugins/woocommerce
|
||||||
env:
|
env:
|
||||||
WC_E2E_SCREENSHOTS: 1
|
|
||||||
E2E_SLACK_TOKEN: ${{ secrets.SMOKE_TEST_SLACK_TOKEN }}
|
|
||||||
E2E_SLACK_CHANNEL: ${{ secrets.SMOKE_TEST_SLACK_CHANNEL }}
|
|
||||||
PLUGIN_REPOSITORY: ${{ matrix.private && secrets[matrix.repo] || matrix.repo }}
|
PLUGIN_REPOSITORY: ${{ matrix.private && secrets[matrix.repo] || matrix.repo }}
|
||||||
PLUGIN_NAME: ${{ matrix.plugin }}
|
PLUGIN_NAME: ${{ matrix.plugin }}
|
||||||
GITHUB_TOKEN: ${{ secrets.E2E_GH_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.E2E_GH_TOKEN }}
|
||||||
run: |
|
run: pnpm exec playwright test --config=tests/e2e-pw/playwright.config.js upload-plugin.spec.js
|
||||||
pnpm exec wc-e2e test:e2e tests/e2e/specs/smoke-tests/upload-plugin.js
|
|
||||||
pnpm exec wc-e2e test:e2e
|
|
||||||
|
|
||||||
publish-test-reports:
|
- name: Run the rest of E2E tests
|
||||||
name: Publish test reports
|
id: e2e
|
||||||
if: always()
|
working-directory: plugins/woocommerce
|
||||||
|
env:
|
||||||
|
E2E_MAX_FAILURES: 15
|
||||||
|
run: pnpm exec playwright test --config=tests/e2e-pw/playwright.config.js basic.spec.js
|
||||||
|
|
||||||
|
- name: Generate E2E Test report.
|
||||||
|
id: report
|
||||||
|
if: |
|
||||||
|
always() &&
|
||||||
|
(
|
||||||
|
steps.e2e-upload.conclusion != 'cancelled' ||
|
||||||
|
steps.e2e-upload.conclusion != 'skipped' ||
|
||||||
|
steps.e2e.conclusion != 'cancelled' ||
|
||||||
|
steps.e2e.conclusion != 'skipped'
|
||||||
|
)
|
||||||
|
working-directory: plugins/woocommerce
|
||||||
|
run: pnpm exec allure generate --clean ${{ env.ALLURE_RESULTS_DIR }} --output ${{ env.ALLURE_REPORT_DIR }}
|
||||||
|
|
||||||
|
- name: Archive E2E test report
|
||||||
|
if: |
|
||||||
|
always() &&
|
||||||
|
steps.report.conclusion == 'success'
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: Smoke tests with ${{ matrix.plugin }} plugin installed (run ${{ github.run_number }})
|
||||||
|
path: |
|
||||||
|
${{ env.ALLURE_RESULTS_DIR }}
|
||||||
|
${{ env.ALLURE_REPORT_DIR }}
|
||||||
|
if-no-files-found: ignore
|
||||||
|
retention-days: 5
|
||||||
|
|
||||||
|
trunk-results:
|
||||||
|
name: Publish report on smoke tests on trunk
|
||||||
|
if: always() &&
|
||||||
|
! github.event.pull_request.head.repo.fork
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
needs: [login-run, build, test-plugins]
|
needs: [test-plugins]
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }}
|
|
||||||
RUN_ID: ${{ github.run_id }}
|
|
||||||
API_ARTIFACT: api-test-report---daily
|
|
||||||
COMMIT_MESSAGE: ${{ needs.login-run.outputs.commit_message }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Publish API test report
|
- name: Create dirs
|
||||||
|
run: |
|
||||||
|
mkdir -p repo
|
||||||
|
mkdir -p artifacts/api
|
||||||
|
mkdir -p artifacts/e2e
|
||||||
|
mkdir -p output
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
path: repo
|
||||||
|
ref: ${{ env.BRANCH_NAME }}
|
||||||
|
|
||||||
|
- name: Download API test report artifact
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: ${{ env.API_ARTIFACT }}
|
||||||
|
path: artifacts/api
|
||||||
|
|
||||||
|
- name: Download E2E test report artifact
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: ${{ env.E2E_ARTIFACT }}
|
||||||
|
path: artifacts/e2e
|
||||||
|
|
||||||
|
- name: Post test summary
|
||||||
|
uses: actions/github-script@v6
|
||||||
|
env:
|
||||||
|
API_SUMMARY_PATH: ${{ github.workspace }}/artifacts/api/allure-report/widgets/summary.json
|
||||||
|
E2E_PW_SUMMARY_PATH: ${{ github.workspace }}/artifacts/e2e/allure-report/widgets/summary.json
|
||||||
|
with:
|
||||||
|
result-encoding: string
|
||||||
|
script: |
|
||||||
|
const script = require( './repo/.github/workflows/scripts/prepare-test-summary-daily.js' )
|
||||||
|
return await script( { core } )
|
||||||
|
|
||||||
|
- name: Publish report
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }}
|
||||||
|
RUN_ID: ${{ github.run_id }}
|
||||||
run: |
|
run: |
|
||||||
gh workflow run publish-test-reports-daily.yml \
|
gh workflow run publish-test-reports-daily.yml \
|
||||||
-f run_id=$RUN_ID \
|
-f run_id=$RUN_ID \
|
||||||
-f api_artifact=$API_ARTIFACT \
|
-f api_artifact="$API_ARTIFACT" \
|
||||||
-f commit_message="$COMMIT_MESSAGE" \
|
-f e2e_artifact="$E2E_ARTIFACT" \
|
||||||
|
-f s3_root=public \
|
||||||
|
--repo woocommerce/woocommerce-test-reports
|
||||||
|
|
||||||
|
plugins-results:
|
||||||
|
name: Publish report on smoke tests with plugins
|
||||||
|
if: |
|
||||||
|
always() &&
|
||||||
|
! github.event.pull_request.head.repo.fork
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
needs: [test-plugins]
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }}
|
||||||
|
RUN_ID: ${{ github.run_id }}
|
||||||
|
ARTIFACT: Smoke tests with ${{ matrix.plugin }} plugin installed (run ${{ github.run_number }})
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- 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: '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:
|
||||||
|
- name: Download test report artifact
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: ${{ env.ARTIFACT }}
|
||||||
|
|
||||||
|
# TODO: Add step to post job summary
|
||||||
|
|
||||||
|
- name: Get slug
|
||||||
|
id: get-slug
|
||||||
|
uses: actions/github-script@v6
|
||||||
|
with:
|
||||||
|
result-encoding: string
|
||||||
|
script: return "${{ matrix.repo }}".split( '/' ).pop()
|
||||||
|
|
||||||
|
- name: Publish reports
|
||||||
|
run: |
|
||||||
|
gh workflow run publish-test-reports-daily-plugins.yml \
|
||||||
|
-f run_id=$RUN_ID \
|
||||||
|
-f artifact="${{ env.ARTIFACT }}" \
|
||||||
|
-f plugin="${{ matrix.plugin }}" \
|
||||||
|
-f slug="${{ steps.get-slug.outputs.result }}" \
|
||||||
|
-f s3_root=public \
|
||||||
--repo woocommerce/woocommerce-test-reports
|
--repo woocommerce/woocommerce-test-reports
|
||||||
|
|
129
changelog.txt
129
changelog.txt
|
@ -1,5 +1,134 @@
|
||||||
== Changelog ==
|
== Changelog ==
|
||||||
|
|
||||||
|
= 7.1.0 2022-11-08 =
|
||||||
|
|
||||||
|
**WooCommerce**
|
||||||
|
|
||||||
|
* Fix - Fix business details step when Gutenberg is active [#35448](https://github.com/woocommerce/woocommerce/pull/35448)
|
||||||
|
* Fix - Check order type is set before returning to prevent notice. [#35349](https://github.com/woocommerce/woocommerce/pull/35349)
|
||||||
|
* Fix - When HPOS is enabled, posts are authoritative, and sync is enabled, ensure the HPOS record correctly tracks the CPT order record. [#35402](https://github.com/woocommerce/woocommerce/pull/35402)
|
||||||
|
* Fix - Allow line breaks in order note again. [#35366](https://github.com/woocommerce/woocommerce/pull/35366)
|
||||||
|
* Fix - Sync orders for stats table. [#35118](https://github.com/woocommerce/woocommerce/pull/35118)
|
||||||
|
* Fix - Fix (un)trashing of orders when using HPOS [#35125](https://github.com/woocommerce/woocommerce/pull/35125)
|
||||||
|
* Fix - Check whether order has classname before returning. [#35207](https://github.com/woocommerce/woocommerce/pull/35207)
|
||||||
|
* Fix - Add billing and shipping address indexes on order update. [#35121](https://github.com/woocommerce/woocommerce/pull/35121)
|
||||||
|
* Fix - Use correct datastore when backfilling orders. [#35176](https://github.com/woocommerce/woocommerce/pull/35176)
|
||||||
|
* Fix - (HPOS) Ensure we use GMT when populating the `date_created_gmt` column for orders. [#34875](https://github.com/woocommerce/woocommerce/pull/34875)
|
||||||
|
* Fix - Admin list table for orders (in HPOS mode) should check in case the user pages beyond the available range. [#34793](https://github.com/woocommerce/woocommerce/pull/34793)
|
||||||
|
* Fix - Allow features to declare their initial enabled state. [#34867](https://github.com/woocommerce/woocommerce/pull/34867)
|
||||||
|
* Fix - Customers should be able to pay for orders so long as any required stock reductions have already taken place. [#33575](https://github.com/woocommerce/woocommerce/pull/33575)
|
||||||
|
* Fix - Do no override order defaults with NULL values (HPOS) [#34822](https://github.com/woocommerce/woocommerce/pull/34822)
|
||||||
|
* Fix - Fix "Industry" options fails to save in the Industry step after reloading the page for OBW [#34847](https://github.com/woocommerce/woocommerce/pull/34847)
|
||||||
|
* Fix - Fix a fatal error thrown by init_theorder_object due to the return type declaration [#34730](https://github.com/woocommerce/woocommerce/pull/34730)
|
||||||
|
* Fix - fixed mismatching jetpack user should not see mobile app task list item [#35052](https://github.com/woocommerce/woocommerce/pull/35052)
|
||||||
|
* Fix - Fix enable guided mode button not trigger when its text is translated [#34843](https://github.com/woocommerce/woocommerce/pull/34843)
|
||||||
|
* Fix - Fixes test environment setup setting datetime for customer user creation [#34888](https://github.com/woocommerce/woocommerce/pull/34888)
|
||||||
|
* Fix - Fix JSON schema for product's image properties. [#34852](https://github.com/woocommerce/woocommerce/pull/34852)
|
||||||
|
* Fix - Fix obw validation issue to truly disable the continue buttons [#34895](https://github.com/woocommerce/woocommerce/pull/34895)
|
||||||
|
* Fix - Fix onboarding wizard popover padding for WP6.1 [#34896](https://github.com/woocommerce/woocommerce/pull/34896)
|
||||||
|
* Fix - Fix order refund removal when the HPOS datastore is in use. [#34785](https://github.com/woocommerce/woocommerce/pull/34785)
|
||||||
|
* Fix - Handle loading and error states for magic link button [#35068](https://github.com/woocommerce/woocommerce/pull/35068)
|
||||||
|
* Fix - Implement missing method of calculating shipping and total tax. [#34805](https://github.com/woocommerce/woocommerce/pull/34805)
|
||||||
|
* Fix - Serialize meta value before rendering so that it's rendered properly. [#34952](https://github.com/woocommerce/woocommerce/pull/34952)
|
||||||
|
* Fix - Set correct timezone when backfilling data. [#35033](https://github.com/woocommerce/woocommerce/pull/35033)
|
||||||
|
* Add - Twenty Twenty-Three theme compatibility. [#35306](https://github.com/woocommerce/woocommerce/pull/35306)
|
||||||
|
* Add - Add handling for plugin-feature incompatibilities [#34879](https://github.com/woocommerce/woocommerce/pull/34879)
|
||||||
|
* Add - Add inventory stock management to new product management experience [#34984](https://github.com/woocommerce/woocommerce/pull/34984)
|
||||||
|
* Add - Add new attributes section and field for the new Product Management Experience. [#34751](https://github.com/woocommerce/woocommerce/pull/34751)
|
||||||
|
* Add - Add order preview functionality to HPOS list table. [#34770](https://github.com/woocommerce/woocommerce/pull/34770)
|
||||||
|
* Add - Add playwright api-core-tests for customers crud operations [#34945](https://github.com/woocommerce/woocommerce/pull/34945)
|
||||||
|
* Add - Add playwright api-core-tests for order notes crud operations [#34979](https://github.com/woocommerce/woocommerce/pull/34979)
|
||||||
|
* Add - Add playwright api-core-tests for product properties crud operations [#34998](https://github.com/woocommerce/woocommerce/pull/34998)
|
||||||
|
* Add - Add playwright api-core-tests for tax rates crud operations [#34960](https://github.com/woocommerce/woocommerce/pull/34960)
|
||||||
|
* Add - Add shipping class section and dropdown [#34684](https://github.com/woocommerce/woocommerce/pull/34684)
|
||||||
|
* Add - Add shipping dimensions section to product page #34329 [#34856](https://github.com/woocommerce/woocommerce/pull/34856)
|
||||||
|
* Add - Add SKU field to new product management experience [#34978](https://github.com/woocommerce/woocommerce/pull/34978)
|
||||||
|
* Add - Add the WooCommerce features engine [#34727](https://github.com/woocommerce/woocommerce/pull/34727)
|
||||||
|
* Add - Deprecate existing `wp wc cot migrate` command and replace with `wp wc cot sync`. [#34676](https://github.com/woocommerce/woocommerce/pull/34676)
|
||||||
|
* Add - Disable action buttons when product form is invalid [#34658](https://github.com/woocommerce/woocommerce/pull/34658)
|
||||||
|
* Add - Expand attributes list to display attributes list and allow removal and re-ordering. [#34841](https://github.com/woocommerce/woocommerce/pull/34841)
|
||||||
|
* Add - Images Product management [#34769](https://github.com/woocommerce/woocommerce/pull/34769)
|
||||||
|
* Add - Improve on feature incompatibility plugin screens. [#35063](https://github.com/woocommerce/woocommerce/pull/35063)
|
||||||
|
* Add - Render columns via action so that they can be hooked into. [#34900](https://github.com/woocommerce/woocommerce/pull/34900)
|
||||||
|
* Add - Support `wc_customer_bought_product` function in HPOS. [#34931](https://github.com/woocommerce/woocommerce/pull/34931)
|
||||||
|
* Add - The updates will mean that the github workflows use the playwright versions of the api-core-tests rather than the supertest versions of the tests. [#34935](https://github.com/woocommerce/woocommerce/pull/34935)
|
||||||
|
* Update - Don't show feature compatibility warnings for inactive plugins [#35333](https://github.com/woocommerce/woocommerce/pull/35333)
|
||||||
|
* Update - Update WooCommerce Blocks to 8.7.5 [#35428](https://github.com/woocommerce/woocommerce/pull/35428)
|
||||||
|
* Update - Improve the warnings about incompatibilities between plugins and features [#35198](https://github.com/woocommerce/woocommerce/pull/35198)
|
||||||
|
* Update - Additional payment methods on new WCPay promotion page (payment-welcome) [#34581](https://github.com/woocommerce/woocommerce/pull/34581)
|
||||||
|
* Update - Add Tiktok to free grow extensions list [#34953](https://github.com/woocommerce/woocommerce/pull/34953)
|
||||||
|
* Update - Allowing generic item type in new experimental SelectControl. [#34547](https://github.com/woocommerce/woocommerce/pull/34547)
|
||||||
|
* Update - Change order data store internal key to props for better representation. [#34627](https://github.com/woocommerce/woocommerce/pull/34627)
|
||||||
|
* Update - Changing inbox display to only 5 notes with the ability to load more. [#35003](https://github.com/woocommerce/woocommerce/pull/35003)
|
||||||
|
* Update - Deploy spotlight product tour treatment [#34859](https://github.com/woocommerce/woocommerce/pull/34859)
|
||||||
|
* Update - Track orders origin in WC_Tracker. [#35069](https://github.com/woocommerce/woocommerce/pull/35069)
|
||||||
|
* Update - Updates a few css selectors to be more robust [#34790](https://github.com/woocommerce/woocommerce/pull/34790)
|
||||||
|
* Update - Update WCPay promo requirements and ensure it's dismissed on every scenario [#35030](https://github.com/woocommerce/woocommerce/pull/35030)
|
||||||
|
* Dev - Add api-core-tests for playwright [#34835](https://github.com/woocommerce/woocommerce/pull/34835)
|
||||||
|
* Dev - Add fail-fast configuration to Playwright E2E tests. [#33977](https://github.com/woocommerce/woocommerce/pull/33977)
|
||||||
|
* Dev - Add new shippping class modal to a shipping class section in product page [#34937](https://github.com/woocommerce/woocommerce/pull/34937)
|
||||||
|
* Dev - Add shipping dimensions image to visualize the sizes of the product #34329 [#34857](https://github.com/woocommerce/woocommerce/pull/34857)
|
||||||
|
* Dev - Add tests for UI Revamp on Marketing Page. [#34840](https://github.com/woocommerce/woocommerce/pull/34840)
|
||||||
|
* Dev - Exclude "debug" module from babel compile to fix the tour kit stories loading error [#34831](https://github.com/woocommerce/woocommerce/pull/34831)
|
||||||
|
* Dev - Fix node and pnpm versions via engines [#34773](https://github.com/woocommerce/woocommerce/pull/34773)
|
||||||
|
* Dev - Improve the matching of plugins during the compatibility check. [#35070](https://github.com/woocommerce/woocommerce/pull/35070)
|
||||||
|
* Dev - Load size units to show it as a suffix of shipping dimensions fields #34329 [#34856](https://github.com/woocommerce/woocommerce/pull/34856)
|
||||||
|
* Dev - Match TypeScript version with syncpack [#34787](https://github.com/woocommerce/woocommerce/pull/34787)
|
||||||
|
* Dev - set the store country in the test step [#34972](https://github.com/woocommerce/woocommerce/pull/34972)
|
||||||
|
* Dev - Update Playwright to 1.26.0 and fix a few flaky tests [#34790](https://github.com/woocommerce/woocommerce/pull/34790)
|
||||||
|
* Dev - Update pnpm version constraint to 7.13.3 to avoid auto-install-peers issues [#35007](https://github.com/woocommerce/woocommerce/pull/35007)
|
||||||
|
* Tweak - Add hooks that fire before an HPOS order is deleted or trashed. [#34858](https://github.com/woocommerce/woocommerce/pull/34858)
|
||||||
|
* Tweak - Disable new-product-management-experience feature flag in development. [#34836](https://github.com/woocommerce/woocommerce/pull/34836)
|
||||||
|
* Tweak - Update copy in the payments welcome modal [#35031](https://github.com/woocommerce/woocommerce/pull/35031)
|
||||||
|
* Tweak - Update subdivision codes for New Zealand, to match current CLDR specification. [#35011](https://github.com/woocommerce/woocommerce/pull/35011)
|
||||||
|
* Tweak - When the primary order store is the posts table, and sync is enabled, propagate changes outside of dedicated migrations. [#34863](https://github.com/woocommerce/woocommerce/pull/34863)
|
||||||
|
* Performance - Support fetching order types in bulk to improve performance. [#34976](https://github.com/woocommerce/woocommerce/pull/34976)
|
||||||
|
* Enhancement - Add support for complex field queries for orders. [#34533](https://github.com/woocommerce/woocommerce/pull/34533)
|
||||||
|
* Enhancement - Also read from posts when reading from orders as a mittigation to direct write. [#34465](https://github.com/woocommerce/woocommerce/pull/34465)
|
||||||
|
* Enhancement - Enable async typeahead fields for the attribute and term fields within products. [#34744](https://github.com/woocommerce/woocommerce/pull/34744)
|
||||||
|
* Enhancement - Enchance tour experience for store location [#34697](https://github.com/woocommerce/woocommerce/pull/34697)
|
||||||
|
|
||||||
|
**WooCommerce Blocks 8.7.0 & 8.7.1 & 8.7.2 & 8.7.3 & 8.7.4 & 8.7.5**
|
||||||
|
|
||||||
|
* Enhancement - Improve visual consistency between block links. ([7340](https://github.com/woocommerce/woocommerce-blocks/pull/7340))
|
||||||
|
* Enhancement - Update the titles of some inner blocks of the Cart block and remove the lock of the Cross-Sells parent block. ([7232](https://github.com/woocommerce/woocommerce-blocks/pull/7232))
|
||||||
|
* Enhancement - Add filter for place order button label. ([7154](https://github.com/woocommerce/woocommerce-blocks/pull/7154))
|
||||||
|
* Enhancement - Exposed data related to the checkout through wordpress/data stores. ([6612](https://github.com/woocommerce/woocommerce-blocks/pull/6612))
|
||||||
|
* Enhancement - Add simple, large & two menus footer patterns. ([7306](https://github.com/woocommerce/woocommerce-blocks/pull/7306))
|
||||||
|
* Enhancement - Add minimal, large, and essential header patterns. ([7292](https://github.com/woocommerce/woocommerce-blocks/pull/7292))
|
||||||
|
* Enhancement - Add `showRemoveItemLink` as a new checkout filter to allow extensions to toggle the visibility of the `Remove item` button under each cart line item. ([7242](https://github.com/woocommerce/woocommerce-blocks/pull/7242))
|
||||||
|
* Enhancement - Add support for a GT tracking ID for Google Analytics. ([7213](https://github.com/woocommerce/woocommerce-blocks/pull/7213))
|
||||||
|
* Enhancement - Separate filter titles and filter controls by converting filter blocks to use Inner Blocks. ([6978](https://github.com/woocommerce/woocommerce-blocks/pull/6978))
|
||||||
|
* Enhancement - StoreApi requests will return a `Cart-Token` header that can be used to retrieve the cart from the corresponding session via **GET** `/wc/store/v1/cart`. ([5953](https://github.com/woocommerce/woocommerce-blocks/pull/5953))
|
||||||
|
* Fix - Fixed HTML rendering in description of active payment integrations. ([7313](https://github.com/woocommerce/woocommerce-blocks/pull/7313))
|
||||||
|
* Fix - Hide the shipping address form from the Checkout when the "Force shipping to the customer billing address" is enabled. ([7268](https://github.com/woocommerce/woocommerce-blocks/pull/7268))
|
||||||
|
* Fix - Fixed an error where adding new pages would cause an infinite loop and large amounts of memory use in redux. ([7256](https://github.com/woocommerce/woocommerce-blocks/pull/7256))
|
||||||
|
* Fix - Ensure error messages containing HTML are shown correctly in the Cart and Checkout blocks. ([7231](https://github.com/woocommerce/woocommerce-blocks/pull/7231))
|
||||||
|
* Fix - Prevent locked inner blocks from sometimes displaying twice. ([6676](https://github.com/woocommerce/woocommerce-blocks/pull/6676))
|
||||||
|
* Fix - StoreApi `/checkout` endpoint now returns HTTP 402 instead of HTTP 400 when payment fails. ([7273](https://github.com/woocommerce/woocommerce-blocks/pull/7273))
|
||||||
|
* Fix - Fix a problem that causes an infinite loop when inserting Cart block in wordpress.com. ([7367](https://github.com/woocommerce/woocommerce-blocks/pull/7367))
|
||||||
|
* Fix - Fixed an issue where JavaScript errors would occur when more than one extension tried to filter specific payment methods in the Cart and Checkout blocks. ([7377](https://github.com/woocommerce/woocommerce-blocks/pull/7377))
|
||||||
|
* Fix - Fixed a problem where Custom Order Tables compatibility declaration could fail due to the unpredictable plugin order load. ([7395](https://github.com/woocommerce/woocommerce-blocks/pull/7395))
|
||||||
|
* Fix - Refactor useCheckoutAddress hook to enable "Use same address for billing" option in Editor ([7393](https://github.com/woocommerce/woocommerce-blocks/pull/7393))
|
||||||
|
* Fix - Fixed an issue where the argument passed to `canMakePayment` contained the incorrect keys. Also fixed the current user's customer data appearing in the editor when editing the Checkout block.
|
||||||
|
* Fix - Compatibility fix for Cart and Checkout inner blocks for WordPress 6.1.
|
||||||
|
|
||||||
|
= 7.0.1 2022-11-01 =
|
||||||
|
|
||||||
|
**WooCommerce**
|
||||||
|
|
||||||
|
* Dev - Twenty Twenty-Three theme compatibility. [#35306](https://github.com/woocommerce/woocommerce/pull/35306)
|
||||||
|
* Dev - Simplify and reduce size of payload supplied by the woocommerce_get_customer_details ajax endpoint.
|
||||||
|
|
||||||
|
**WooCommerce Blocks 8.5.2**
|
||||||
|
|
||||||
|
* Enhancement - Fix Mini Cart Global Styles. [7515](https://github.com/woocommerce/woocommerce-blocks/pull/7515)
|
||||||
|
* Enhancement - Fix inconsistent button styling with TT3. ([7516](https://github.com/woocommerce/woocommerce-blocks/pull/7516))
|
||||||
|
* Enhancement - Make the Filter by Price block range color dependent of the theme color. [7525](https://github.com/woocommerce/woocommerce-blocks/pull/7525)
|
||||||
|
* Enhancement - Filter by Price block: fix price slider visibility on dark themes. [7527](https://github.com/woocommerce/woocommerce-blocks/pull/7527)
|
||||||
|
* Enhancement - Update the Mini Cart block drawer to honor the theme's background. [7510](https://github.com/woocommerce/woocommerce-blocks/pull/7510)
|
||||||
|
* Enhancement - Add white background to Filter by Attribute block dropdown so text is legible in dark backgrounds. [7506](https://github.com/woocommerce/woocommerce-blocks/pull/7506)
|
||||||
|
|
||||||
= 7.0.0 2022-10-11 =
|
= 7.0.0 2022-10-11 =
|
||||||
|
|
||||||
**WooCommerce**
|
**WooCommerce**
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
# Unreleased
|
# Unreleased
|
||||||
|
|
||||||
|
# 1.0.0
|
||||||
|
|
||||||
## Changed
|
## Changed
|
||||||
- Bumped jest version to v27
|
- Bumped jest version to v27
|
||||||
- Used the jest packaged bundled in this module to run tests
|
- Used the jest packaged bundled in this module to run tests
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@woocommerce/api-core-tests",
|
"name": "@woocommerce/api-core-tests",
|
||||||
"version": "0.1.0",
|
"version": "1.0.0",
|
||||||
"description": "API tests for WooCommerce",
|
"description": "API tests for WooCommerce",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
|
@ -2,6 +2,59 @@
|
||||||
|
|
||||||
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).
|
||||||
|
|
||||||
|
## [11.1.0](https://www.npmjs.com/package/@woocommerce/components/v/11.1.0) - 2022-10-24
|
||||||
|
|
||||||
|
- Minor - Allow passing of additional props to form inputs [#35160]
|
||||||
|
|
||||||
|
## [11.0.0](https://www.npmjs.com/package/@woocommerce/components/v/11.0.0) - 2022-10-20
|
||||||
|
|
||||||
|
- Patch - Export StepperProps for external usage [#35140]
|
||||||
|
- Patch - Fixed the initial setting of DateTimePickerControl's input field. [#35140]
|
||||||
|
- Patch - Fix EnrichedLabel Storybook story styles so they don't affect other stories. [#35140]
|
||||||
|
- Patch - Fixes DateTimePickerControl's debounce handling to work even if onChange prop changes. [#35140]
|
||||||
|
- Patch - Fix issue with form onChange handler, passing outdated values. [#35140]
|
||||||
|
- Patch - Update tag component styling [#35140]
|
||||||
|
- Patch - Add missing type definitions and add babel config for tests [#35140]
|
||||||
|
- Patch - Merging trunk with local [#35140]
|
||||||
|
- Patch - Removed unfinished and unused SplitDropdown component. [#35140]
|
||||||
|
- Patch - Assume ambiguous dates passed into DateTimePickerControl are UTC. [#35140]
|
||||||
|
- Patch - Remove default selected sortable item. [#35140]
|
||||||
|
- Minor - Fix Enriched-label styles
|
||||||
|
- Minor - Fix initially selected items in SelectControl component [#35140]
|
||||||
|
- Minor - Add date-only mode to DateTimePickerControl. [#35140]
|
||||||
|
- Minor - Add disabled option to the Select Control input component and alter the onInputChange callback [#35140]
|
||||||
|
- Minor - Add form input name dot notation name="product.dimensions.width" [#35140]
|
||||||
|
- Minor - Add FormSection component [#35140]
|
||||||
|
- Minor - Add ImageGallery component [#35140]
|
||||||
|
- Minor - Adding datetimepicker component. [#35140]
|
||||||
|
- Minor - Adding on-click toolbar to image gallery component items. [#35140]
|
||||||
|
- Minor - Add label prop to rich text editor [#35140]
|
||||||
|
- Minor - Add MediaUploader component [#35140]
|
||||||
|
- Minor - Add rich text editor component [#35140]
|
||||||
|
- Minor - Add SortableList component [#35140]
|
||||||
|
- Minor - Allow external tags in SelectControl component [#35140]
|
||||||
|
- Minor - Export ImportProps type. Add DateTimePickerControl to Form stories and tests. [#35140]
|
||||||
|
- Minor - Images Product management [#35140]
|
||||||
|
- Minor - Remove EnrichedLabel component in favor of Tooltip component [#35140]
|
||||||
|
- Minor - Update resetForm arguments, adding changed fields, touched fields and errors. [#35140]
|
||||||
|
- Minor - [PM Components] Create SplitDropdown component. #34180 [#35140]
|
||||||
|
- Minor - Add label, placeholder, and help props to DateTimePickerControl. [#35140]
|
||||||
|
- Minor - Adds setValues support to FormContext [#35140]
|
||||||
|
- Minor - Add support in SelectControl for using the popover slot for the popover. [#35140]
|
||||||
|
- Minor - Update experimental SelectControl compoment to expose a couple extra combobox functions from Downshift. [#35140]
|
||||||
|
- Minor - Update experimental SelectControl compoment to expose combobox functions from Downshift and provide additional options. [#35140]
|
||||||
|
- Minor - Update text input placement in SelectControl [#35140]
|
||||||
|
- Minor - Add component EnrichedLabel #34214 [#35140]
|
||||||
|
- Minor - Add new shippping class modal to a shipping class section in product page [#35140]
|
||||||
|
- Minor - Adjust build/test scripts to remove -- -- that was required for pnpm 6. [#35140]
|
||||||
|
- Minor - Fix node and pnpm versions via engines [#35140]
|
||||||
|
- Minor - Update Plugin installer component to TS [#35140]
|
||||||
|
- Minor - Update pnpm version constraint to 7.13.3 to avoid auto-install-peers issues [#35140]
|
||||||
|
- Minor - Fix DateTimePickerControl's onChange date arg to only be a string (TypeScript). [#35140]
|
||||||
|
- Minor - Improve experimental SelectControl accessibility [#35140]
|
||||||
|
- Minor - Improve Sortable component acessibility [#35140]
|
||||||
|
- - Create new experimental SelectControl component [#35140]
|
||||||
|
|
||||||
## [10.3.0](https://www.npmjs.com/package/@woocommerce/components/v/10.3.0) - 2022-08-12
|
## [10.3.0](https://www.npmjs.com/package/@woocommerce/components/v/10.3.0) - 2022-08-12
|
||||||
|
|
||||||
- Patch - Added in missing TS definitions in package.json [#34279]
|
- Patch - Added in missing TS definitions in package.json [#34279]
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: minor
|
|
||||||
Type: dev
|
|
||||||
|
|
||||||
Add component EnrichedLabel #34214
|
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: minor
|
|
||||||
Type: add
|
|
||||||
|
|
||||||
Update resetForm arguments, adding changed fields, touched fields and errors.
|
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: minor
|
|
||||||
Type: fix
|
|
||||||
|
|
||||||
Fix Enriched-label styles - #34382
|
|
|
@ -1,4 +1,4 @@
|
||||||
Significance: patch
|
Significance: patch
|
||||||
Type: update
|
Type: update
|
||||||
|
|
||||||
Update tag component styling
|
Updating downshift to 6.1.12.
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: patch
|
|
||||||
Type: tweak
|
|
||||||
|
|
||||||
Remove default selected sortable item.
|
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: minor
|
|
||||||
Type: add
|
|
||||||
|
|
||||||
Adding on-click toolbar to image gallery component items.
|
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: minor
|
|
||||||
Type: dev
|
|
||||||
|
|
||||||
Add new shippping class modal to a shipping class section in product page
|
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: minor
|
|
||||||
Type: update
|
|
||||||
|
|
||||||
Update experimental SelectControl compoment to expose a couple extra combobox functions from Downshift.
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: minor
|
||||||
|
Type: add
|
||||||
|
|
||||||
|
Add experimental ConditionalWrapper component
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: minor
|
|
||||||
Type: add
|
|
||||||
|
|
||||||
Images Product management
|
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: minor
|
|
||||||
Type: add
|
|
||||||
|
|
||||||
Adding datetimepicker component.
|
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: minor
|
|
||||||
Type: add
|
|
||||||
|
|
||||||
Add SortableList component
|
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: minor
|
|
||||||
Type: add
|
|
||||||
|
|
||||||
Add form input name dot notation name="product.dimensions.width"
|
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: minor
|
|
||||||
Type: add
|
|
||||||
|
|
||||||
Add rich text editor component
|
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: minor
|
|
||||||
Type: add
|
|
||||||
|
|
||||||
Add ImageGallery component
|
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: minor
|
|
||||||
Type: add
|
|
||||||
|
|
||||||
Add MediaUploader component
|
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: minor
|
|
||||||
Type: update
|
|
||||||
|
|
||||||
Adds setValues support to FormContext
|
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: minor
|
|
||||||
Type: add
|
|
||||||
|
|
||||||
Add FormSection component
|
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: minor
|
|
||||||
Type: enhancement
|
|
||||||
|
|
||||||
Improve Sortable component acessibility
|
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: minor
|
|
||||||
Type: add
|
|
||||||
|
|
||||||
[PM Components] Create SplitDropdown component. #34180
|
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: minor
|
|
||||||
Type: dev
|
|
||||||
|
|
||||||
Update pnpm version constraint to 7.13.3 to avoid auto-install-peers issues
|
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: patch
|
|
||||||
Type: dev
|
|
||||||
|
|
||||||
Add missing type definitions and add babel config for tests
|
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: minor
|
|
||||||
Type: dev
|
|
||||||
|
|
||||||
Fix node and pnpm versions via engines
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: patch
|
||||||
|
Type: enhancement
|
||||||
|
|
||||||
|
Update font size and spacing in the tooltip component
|
|
@ -1,5 +0,0 @@
|
||||||
Significance: patch
|
|
||||||
Type: tweak
|
|
||||||
Comment: Minor update of react and react-dom to 17.0.2.
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: patch
|
|
||||||
Type: fix
|
|
||||||
|
|
||||||
Fix issue with form onChange handler, passing outdated values.
|
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: patch
|
|
||||||
Type: fix
|
|
||||||
|
|
||||||
Fixes DateTimePickerControl's debounce handling to work even if onChange prop changes.
|
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: patch
|
|
||||||
Type: fix
|
|
||||||
|
|
||||||
Fixed the initial setting of DateTimePickerControl's input field.
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: patch
|
||||||
|
Type: fix
|
||||||
|
|
||||||
|
DateTimePickerControl's onChange now only fires when there is an actual change to the datetime.
|
|
@ -0,0 +1,5 @@
|
||||||
|
Significance: patch
|
||||||
|
Type: fix
|
||||||
|
Comment: Just a minor tweak to the CSS for the DateTimePickerControl suffix.
|
||||||
|
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: patch
|
|
||||||
Type: fix
|
|
||||||
|
|
||||||
Fix EnrichedLabel Storybook story styles so they don't affect other stories.
|
|
|
@ -1,6 +0,0 @@
|
||||||
Significance: patch
|
|
||||||
Type: fix
|
|
||||||
|
|
||||||
Export StepperProps for external usage
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: patch
|
||||||
|
Type: tweak
|
||||||
|
|
||||||
|
Update variable name within useFormContext.
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: minor
|
|
||||||
Type: dev
|
|
||||||
|
|
||||||
Update Plugin installer component to TS
|
|
|
@ -1,5 +0,0 @@
|
||||||
Significance: patch
|
|
||||||
Type: tweak
|
|
||||||
Comment: Reverted change of last PR as part of #34614
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: minor
|
||||||
|
Type: tweak
|
||||||
|
|
||||||
|
Fix up initial block selection in RichTextEditor and add media blocks
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: minor
|
|
||||||
Type: enhancement
|
|
||||||
|
|
||||||
Improve experimental SelectControl accessibility
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: patch
|
||||||
|
Type: fix
|
||||||
|
|
||||||
|
Add name to exported popover slot used to display SelectControl Menu, so it is only used for SelectControl menus.
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: minor
|
|
||||||
Type: fix
|
|
||||||
|
|
||||||
Fix initially selected items in SelectControl component
|
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: patch
|
|
||||||
Type: dev
|
|
||||||
|
|
||||||
Removed unfinished and unused SplitDropdown component.
|
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: major
|
|
||||||
Type: add
|
|
||||||
|
|
||||||
Create new experimental SelectControl component
|
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: minor
|
|
||||||
Type: update
|
|
||||||
|
|
||||||
Add label, placeholder, and help props to DateTimePickerControl.
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: minor
|
||||||
|
Type: add
|
||||||
|
|
||||||
|
Added ability to force time when DateTimePickerControl is date-only (timeForDateOnly prop).
|
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: major
|
||||||
|
Type: update
|
||||||
|
|
||||||
|
Switch DateTimePickerControl formatting to PHP style, for WP compatibility.
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: minor
|
|
||||||
Type: tweak
|
|
||||||
|
|
||||||
Fix DateTimePickerControl's onChange date arg to only be a string (TypeScript).
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: patch
|
||||||
|
Type: fix
|
||||||
|
|
||||||
|
Fix DateTimePickerControl's popover styling when slot-fill is used.
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: minor
|
|
||||||
Type: update
|
|
||||||
|
|
||||||
Update experimental SelectControl compoment to expose combobox functions from Downshift and provide additional options.
|
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: minor
|
|
||||||
Type: update
|
|
||||||
|
|
||||||
Update text input placement in SelectControl
|
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: minor
|
|
||||||
Type: add
|
|
||||||
|
|
||||||
Allow external tags in SelectControl component
|
|
|
@ -1,4 +0,0 @@
|
||||||
Significance: minor
|
|
||||||
Type: dev
|
|
||||||
|
|
||||||
Adjust build/test scripts to remove -- -- that was required for pnpm 6.
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@woocommerce/components",
|
"name": "@woocommerce/components",
|
||||||
"version": "10.3.0",
|
"version": "11.1.0",
|
||||||
"description": "UI components for WooCommerce.",
|
"description": "UI components for WooCommerce.",
|
||||||
"author": "Automattic",
|
"author": "Automattic",
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
|
@ -74,7 +74,7 @@
|
||||||
"d3-shape": "^1.3.7",
|
"d3-shape": "^1.3.7",
|
||||||
"d3-time-format": "^2.3.0",
|
"d3-time-format": "^2.3.0",
|
||||||
"dompurify": "^2.3.6",
|
"dompurify": "^2.3.6",
|
||||||
"downshift": "^6.1.9",
|
"downshift": "^6.1.12",
|
||||||
"emoji-flags": "^1.3.0",
|
"emoji-flags": "^1.3.0",
|
||||||
"gridicons": "^3.4.0",
|
"gridicons": "^3.4.0",
|
||||||
"memoize-one": "^6.0.0",
|
"memoize-one": "^6.0.0",
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
.woocommerce-date-time-picker-control {
|
.woocommerce-date-time-picker-control {
|
||||||
display: block;
|
display: block;
|
||||||
|
|
||||||
.woocommerce-date-time-picker-control__input-control__suffix {
|
.components-input-control__suffix {
|
||||||
padding-right: 8px;
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__popover {
|
||||||
|
.components-datetime__date {
|
||||||
|
border-top: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
/**
|
/**
|
||||||
* External dependencies
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
|
import { format as formatDate } from '@wordpress/date';
|
||||||
import {
|
import {
|
||||||
createElement,
|
createElement,
|
||||||
|
useCallback,
|
||||||
useState,
|
useState,
|
||||||
useEffect,
|
useEffect,
|
||||||
useLayoutEffect,
|
useMemo,
|
||||||
useCallback,
|
|
||||||
useRef,
|
useRef,
|
||||||
} from '@wordpress/element';
|
} from '@wordpress/element';
|
||||||
import { Icon, calendar } from '@wordpress/icons';
|
import { Icon, calendar } from '@wordpress/icons';
|
||||||
|
@ -16,17 +17,21 @@ import { sprintf, __ } from '@wordpress/i18n';
|
||||||
import { useDebounce, useInstanceId } from '@wordpress/compose';
|
import { useDebounce, useInstanceId } from '@wordpress/compose';
|
||||||
import {
|
import {
|
||||||
BaseControl,
|
BaseControl,
|
||||||
Dropdown,
|
DatePicker,
|
||||||
DateTimePicker as WpDateTimePicker,
|
DateTimePicker as WpDateTimePicker,
|
||||||
|
Dropdown,
|
||||||
// @ts-expect-error `__experimentalInputControl` does exist.
|
// @ts-expect-error `__experimentalInputControl` does exist.
|
||||||
__experimentalInputControl as InputControl,
|
__experimentalInputControl as InputControl,
|
||||||
} from '@wordpress/components';
|
} from '@wordpress/components';
|
||||||
|
|
||||||
export const default12HourDateTimeFormat = 'MM/DD/YYYY h:mm a';
|
// PHP style formatting:
|
||||||
export const default24HourDateTimeFormat = 'MM/DD/YYYY H:mm';
|
// https://wordpress.org/support/article/formatting-date-and-time/
|
||||||
|
export const defaultDateFormat = 'm/d/Y';
|
||||||
|
export const default12HourDateTimeFormat = 'm/d/Y h:i a';
|
||||||
|
export const default24HourDateTimeFormat = 'm/d/Y H:i';
|
||||||
|
|
||||||
export type DateTimePickerControlOnChangeHandler = (
|
export type DateTimePickerControlOnChangeHandler = (
|
||||||
date: string,
|
dateTimeIsoString: string,
|
||||||
isValid: boolean
|
isValid: boolean
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
|
@ -34,7 +39,9 @@ export type DateTimePickerControlProps = {
|
||||||
currentDate?: string | null;
|
currentDate?: string | null;
|
||||||
dateTimeFormat?: string;
|
dateTimeFormat?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
is12Hour?: boolean;
|
isDateOnlyPicker?: boolean;
|
||||||
|
is12HourPicker?: boolean;
|
||||||
|
timeForDateOnly?: 'start-of-day' | 'end-of-day';
|
||||||
onChange?: DateTimePickerControlOnChangeHandler;
|
onChange?: DateTimePickerControlOnChangeHandler;
|
||||||
onBlur?: () => void;
|
onBlur?: () => void;
|
||||||
label?: string;
|
label?: string;
|
||||||
|
@ -45,10 +52,10 @@ export type DateTimePickerControlProps = {
|
||||||
|
|
||||||
export const DateTimePickerControl: React.FC< DateTimePickerControlProps > = ( {
|
export const DateTimePickerControl: React.FC< DateTimePickerControlProps > = ( {
|
||||||
currentDate,
|
currentDate,
|
||||||
is12Hour = true,
|
isDateOnlyPicker = false,
|
||||||
dateTimeFormat = is12Hour
|
is12HourPicker = true,
|
||||||
? default12HourDateTimeFormat
|
timeForDateOnly = 'start-of-day',
|
||||||
: default24HourDateTimeFormat,
|
dateTimeFormat,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
onChange,
|
onChange,
|
||||||
onBlur,
|
onBlur,
|
||||||
|
@ -62,35 +69,59 @@ export const DateTimePickerControl: React.FC< DateTimePickerControlProps > = ( {
|
||||||
const id = `inspector-date-time-picker-control-${ instanceId }`;
|
const id = `inspector-date-time-picker-control-${ instanceId }`;
|
||||||
const inputControl = useRef< InputControl >();
|
const inputControl = useRef< InputControl >();
|
||||||
|
|
||||||
const isMounted = useRef( false );
|
|
||||||
useEffect( () => {
|
|
||||||
isMounted.current = true;
|
|
||||||
return () => {
|
|
||||||
isMounted.current = false;
|
|
||||||
};
|
|
||||||
} );
|
|
||||||
|
|
||||||
const [ inputString, setInputString ] = useState( '' );
|
const [ inputString, setInputString ] = useState( '' );
|
||||||
const [ lastValidDate, setLastValidDate ] = useState< Moment | null >(
|
|
||||||
null
|
const displayFormat = useMemo( () => {
|
||||||
|
if ( dateTimeFormat ) {
|
||||||
|
return dateTimeFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isDateOnlyPicker ) {
|
||||||
|
return defaultDateFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( is12HourPicker ) {
|
||||||
|
return default12HourDateTimeFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
return default24HourDateTimeFormat;
|
||||||
|
}, [ dateTimeFormat, isDateOnlyPicker, is12HourPicker ] );
|
||||||
|
|
||||||
|
function parseAsISODateTime(
|
||||||
|
dateString?: string | null,
|
||||||
|
assumeLocalTime = false
|
||||||
|
): Moment {
|
||||||
|
return assumeLocalTime
|
||||||
|
? moment( dateString, moment.ISO_8601, true ).utc()
|
||||||
|
: moment.utc( dateString, moment.ISO_8601, true );
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseAsLocalDateTime( dateString: string | null ): Moment {
|
||||||
|
// parse input date string as local time;
|
||||||
|
// be lenient of user input and try to match any format Moment can
|
||||||
|
return moment( dateString );
|
||||||
|
}
|
||||||
|
|
||||||
|
const maybeForceTime = useCallback(
|
||||||
|
( momentDate: Moment ) => {
|
||||||
|
if ( ! isDateOnlyPicker || ! momentDate.isValid() )
|
||||||
|
return momentDate;
|
||||||
|
|
||||||
|
// We want to set to the start/end of the local time, so
|
||||||
|
// we need to put our Moment instance into "local" mode
|
||||||
|
const updatedMomentDate = momentDate.clone().local();
|
||||||
|
|
||||||
|
if ( timeForDateOnly === 'start-of-day' ) {
|
||||||
|
updatedMomentDate.startOf( 'day' );
|
||||||
|
} else if ( timeForDateOnly === 'end-of-day' ) {
|
||||||
|
updatedMomentDate.endOf( 'day' );
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedMomentDate;
|
||||||
|
},
|
||||||
|
[ isDateOnlyPicker, timeForDateOnly ]
|
||||||
);
|
);
|
||||||
|
|
||||||
function parseMomentIso( dateString?: string | null ): Moment {
|
|
||||||
return moment( dateString, moment.ISO_8601, true );
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseMoment( dateString?: string | null ): Moment {
|
|
||||||
return moment( dateString, dateTimeFormat );
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatMomentIso( momentDate: Moment ): string {
|
|
||||||
return momentDate.toISOString();
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatMoment( momentDate: Moment ): string {
|
|
||||||
return momentDate.format( dateTimeFormat );
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasFocusLeftInputAndDropdownContent(
|
function hasFocusLeftInputAndDropdownContent(
|
||||||
event: React.FocusEvent< HTMLInputElement >
|
event: React.FocusEvent< HTMLInputElement >
|
||||||
): boolean {
|
): boolean {
|
||||||
|
@ -99,89 +130,67 @@ export const DateTimePickerControl: React.FC< DateTimePickerControlProps > = ( {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We setup the debounced handling of the input string changes using
|
const formatDateTimeForDisplay = useCallback(
|
||||||
// useRef because useCallback does *not* guarantee that the resulting
|
( dateTime: Moment ) => {
|
||||||
// callback function will not be recreated, even if the dependencies
|
return dateTime.isValid()
|
||||||
// haven't changed (this is because of it's use of useMemo under the
|
? formatDate( displayFormat, dateTime.local() )
|
||||||
// hood, which also makes not guarantee). And, even if it did, the
|
: dateTime.creationData().input?.toString() || '';
|
||||||
// equality check for useCallback dependencies is by reference. So, if
|
},
|
||||||
// the "same" function is passed in, but it is a different instance, it
|
[ displayFormat ]
|
||||||
// will trigger the recreation of the callback.
|
|
||||||
//
|
|
||||||
// With useDebounce, if the callback function changes, the current
|
|
||||||
// debounce is canceled. This results in the callback function never being
|
|
||||||
// called.
|
|
||||||
//
|
|
||||||
// We *need* to ensure that our handler is called at least once,
|
|
||||||
// and also that we call the passed in onChange callback.
|
|
||||||
//
|
|
||||||
// We guarantee this by keeping references to both our handler and the
|
|
||||||
// passed in prop.
|
|
||||||
//
|
|
||||||
// The consumer of DateTimePickerControl should ensure that the
|
|
||||||
// function passed into onChange does not change (using references or
|
|
||||||
// useCallbackOne). But, even if they do not, and the function changes,
|
|
||||||
// things will likely function as expected unless the consumer is doing
|
|
||||||
// something really convoluted.
|
|
||||||
//
|
|
||||||
// See also:
|
|
||||||
// - [note regarding useMemo not being a semantic guarantee](https://reactjs.org/docs/hooks-reference.html#usememo)
|
|
||||||
// - [useDebounce hook loses function calls if the dependency changes](https://github.com/WordPress/gutenberg/issues/35505)
|
|
||||||
// - [useMemoOne and useCallbackOne](https://github.com/alexreardon/use-memo-one)
|
|
||||||
|
|
||||||
const onChangePropFunctionRef = useRef<
|
|
||||||
DateTimePickerControlOnChangeHandler | undefined
|
|
||||||
>();
|
|
||||||
useLayoutEffect( () => {
|
|
||||||
onChangePropFunctionRef.current = onChange;
|
|
||||||
}, [ onChange ] );
|
|
||||||
|
|
||||||
const inputStringChangeHandlerFunctionRef = useRef<
|
|
||||||
( newInputString: string, fireOnChange: boolean ) => void
|
|
||||||
>( ( newInputString: string, fireOnChange: boolean ) => {
|
|
||||||
if ( ! isMounted.current ) return;
|
|
||||||
|
|
||||||
const newDateTime = parseMoment( newInputString );
|
|
||||||
const isValid = newDateTime.isValid();
|
|
||||||
|
|
||||||
if ( isValid ) {
|
|
||||||
setLastValidDate( newDateTime );
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
fireOnChange &&
|
|
||||||
typeof onChangePropFunctionRef.current === 'function'
|
|
||||||
) {
|
|
||||||
onChangePropFunctionRef.current(
|
|
||||||
isValid ? formatMomentIso( newDateTime ) : newInputString,
|
|
||||||
isValid
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} );
|
|
||||||
|
|
||||||
const debouncedInputStringChangeHandler = useDebounce(
|
|
||||||
inputStringChangeHandlerFunctionRef.current,
|
|
||||||
onChangeDebounceWait
|
|
||||||
);
|
);
|
||||||
|
|
||||||
function change( newInputString: string ) {
|
function formatDateTimeAsISO( dateTime: Moment ): string {
|
||||||
setInputString( newInputString );
|
return dateTime.isValid()
|
||||||
debouncedInputStringChangeHandler( newInputString, true );
|
? dateTime.utc().toISOString()
|
||||||
|
: dateTime.creationData().input?.toString() || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeImmediate( newInputString: string, fireOnChange: boolean ) {
|
const inputStringDateTime = useMemo( () => {
|
||||||
setInputString( newInputString );
|
return maybeForceTime( parseAsLocalDateTime( inputString ) );
|
||||||
inputStringChangeHandlerFunctionRef.current(
|
}, [ inputString, maybeForceTime ] );
|
||||||
newInputString,
|
|
||||||
fireOnChange
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function blur() {
|
// We keep a ref to the onChange prop so that we can be sure we are
|
||||||
if ( onBlur ) {
|
// always using the more up-to-date value, even if it changes
|
||||||
onBlur();
|
// it while a debounced onChange handler is in progress
|
||||||
}
|
const onChangeRef = useRef<
|
||||||
}
|
DateTimePickerControlOnChangeHandler | undefined
|
||||||
|
>();
|
||||||
|
useEffect( () => {
|
||||||
|
onChangeRef.current = onChange;
|
||||||
|
}, [ onChange ] );
|
||||||
|
|
||||||
|
const setInputStringAndMaybeCallOnChange = useCallback(
|
||||||
|
( newInputString: string, isUserTypedInput: boolean ) => {
|
||||||
|
const newDateTime = maybeForceTime(
|
||||||
|
isUserTypedInput
|
||||||
|
? parseAsLocalDateTime( newInputString )
|
||||||
|
: parseAsISODateTime( newInputString, true )
|
||||||
|
);
|
||||||
|
const isDateTimeSame = newDateTime.isSame( inputStringDateTime );
|
||||||
|
|
||||||
|
if ( isUserTypedInput ) {
|
||||||
|
setInputString( newInputString );
|
||||||
|
} else if ( ! isDateTimeSame ) {
|
||||||
|
setInputString( formatDateTimeForDisplay( newDateTime ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof onChangeRef.current === 'function' &&
|
||||||
|
! isDateTimeSame
|
||||||
|
) {
|
||||||
|
onChangeRef.current(
|
||||||
|
formatDateTimeAsISO( newDateTime ),
|
||||||
|
newDateTime.isValid()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[ formatDateTimeForDisplay, inputStringDateTime, maybeForceTime ]
|
||||||
|
);
|
||||||
|
|
||||||
|
const debouncedSetInputStringAndMaybeCallOnChange = useDebounce(
|
||||||
|
setInputStringAndMaybeCallOnChange,
|
||||||
|
onChangeDebounceWait
|
||||||
|
);
|
||||||
|
|
||||||
function focusInputControl() {
|
function focusInputControl() {
|
||||||
if ( inputControl.current ) {
|
if ( inputControl.current ) {
|
||||||
|
@ -189,21 +198,23 @@ export const DateTimePickerControl: React.FC< DateTimePickerControlProps > = ( {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const isInitialUpdate = useRef( true );
|
function getUserInputOrUpdatedCurrentDate() {
|
||||||
useEffect( () => {
|
const newDateTime = maybeForceTime(
|
||||||
const fireOnChange = ! isInitialUpdate.current;
|
parseAsISODateTime( currentDate, false )
|
||||||
if ( isInitialUpdate.current ) {
|
);
|
||||||
isInitialUpdate.current = false;
|
|
||||||
|
if (
|
||||||
|
! newDateTime.isValid() ||
|
||||||
|
newDateTime.isSame(
|
||||||
|
maybeForceTime( parseAsLocalDateTime( inputString ) )
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// keep the input string as the user entered it
|
||||||
|
return inputString;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newDate = parseMomentIso( currentDate );
|
return formatDateTimeForDisplay( newDateTime );
|
||||||
|
}
|
||||||
if ( newDate.isValid() ) {
|
|
||||||
changeImmediate( formatMoment( newDate ), fireOnChange );
|
|
||||||
} else {
|
|
||||||
changeImmediate( currentDate || '', fireOnChange );
|
|
||||||
}
|
|
||||||
}, [ currentDate, dateTimeFormat ] );
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
|
@ -215,8 +226,8 @@ export const DateTimePickerControl: React.FC< DateTimePickerControlProps > = ( {
|
||||||
focusOnMount={ false }
|
focusOnMount={ false }
|
||||||
// @ts-expect-error `onToggle` does exist.
|
// @ts-expect-error `onToggle` does exist.
|
||||||
onToggle={ ( willOpen ) => {
|
onToggle={ ( willOpen ) => {
|
||||||
if ( ! willOpen ) {
|
if ( ! willOpen && typeof onBlur === 'function' ) {
|
||||||
blur();
|
onBlur();
|
||||||
}
|
}
|
||||||
} }
|
} }
|
||||||
renderToggle={ ( { isOpen, onToggle } ) => (
|
renderToggle={ ( { isOpen, onToggle } ) => (
|
||||||
|
@ -225,8 +236,13 @@ export const DateTimePickerControl: React.FC< DateTimePickerControlProps > = ( {
|
||||||
id={ id }
|
id={ id }
|
||||||
ref={ inputControl }
|
ref={ inputControl }
|
||||||
disabled={ disabled }
|
disabled={ disabled }
|
||||||
value={ inputString }
|
value={ getUserInputOrUpdatedCurrentDate() }
|
||||||
onChange={ change }
|
onChange={ ( newValue: string ) =>
|
||||||
|
debouncedSetInputStringAndMaybeCallOnChange(
|
||||||
|
newValue,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
}
|
||||||
onBlur={ (
|
onBlur={ (
|
||||||
event: React.FocusEvent< HTMLInputElement >
|
event: React.FocusEvent< HTMLInputElement >
|
||||||
) => {
|
) => {
|
||||||
|
@ -264,22 +280,30 @@ export const DateTimePickerControl: React.FC< DateTimePickerControlProps > = ( {
|
||||||
/>
|
/>
|
||||||
</BaseControl>
|
</BaseControl>
|
||||||
) }
|
) }
|
||||||
renderContent={ () => (
|
popoverProps={ {
|
||||||
<WpDateTimePicker
|
className: 'woocommerce-date-time-picker-control__popover',
|
||||||
currentDate={
|
} }
|
||||||
lastValidDate
|
renderContent={ () => {
|
||||||
? formatMomentIso( lastValidDate )
|
const Picker = isDateOnlyPicker ? DatePicker : WpDateTimePicker;
|
||||||
: undefined
|
const inputDateTime = parseAsLocalDateTime( inputString );
|
||||||
}
|
|
||||||
onChange={ ( date: string ) => {
|
return (
|
||||||
const formattedDate = formatMoment(
|
<Picker
|
||||||
parseMomentIso( date )
|
currentDate={
|
||||||
);
|
inputDateTime.isValid()
|
||||||
changeImmediate( formattedDate, true );
|
? formatDateTimeAsISO( inputDateTime )
|
||||||
} }
|
: undefined
|
||||||
is12Hour={ is12Hour }
|
}
|
||||||
/>
|
onChange={ ( newDateTimeISOString: string ) =>
|
||||||
) }
|
setInputStringAndMaybeCallOnChange(
|
||||||
|
newDateTimeISOString,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is12Hour={ is12HourPicker }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,13 +2,13 @@
|
||||||
* External dependencies
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Button } from '@wordpress/components';
|
import { Button, Popover, SlotFillProvider } from '@wordpress/components';
|
||||||
import { createElement, useState } from '@wordpress/element';
|
import { createElement, useState } from '@wordpress/element';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import { DateTimePickerControl } from '../';
|
import { DateTimePickerControl, defaultDateFormat } from '../';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'WooCommerce Admin/components/DateTimePickerControl',
|
title: 'WooCommerce Admin/components/DateTimePickerControl',
|
||||||
|
@ -28,16 +28,25 @@ Basic.args = {
|
||||||
help: 'Type a date and time or use the picker',
|
help: 'Type a date and time or use the picker',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const customFormat = 'Y-m-d H:i';
|
||||||
|
|
||||||
export const CustomDateTimeFormat = Template.bind( {} );
|
export const CustomDateTimeFormat = Template.bind( {} );
|
||||||
CustomDateTimeFormat.args = {
|
CustomDateTimeFormat.args = {
|
||||||
...Basic.args,
|
...Basic.args,
|
||||||
help: 'Format: YYYY-MM-DD HH:mm',
|
help: 'Format: ' + customFormat,
|
||||||
dateTimeFormat: 'YYYY-MM-DD HH:mm',
|
dateTimeFormat: customFormat,
|
||||||
};
|
};
|
||||||
|
|
||||||
function ControlledContainer( { children, ...props } ) {
|
function ControlledContainer( { children, ...props } ) {
|
||||||
|
function nowWithZeroedSeconds() {
|
||||||
|
const now = new Date();
|
||||||
|
now.setSeconds( 0 );
|
||||||
|
now.setMilliseconds( 0 );
|
||||||
|
return now;
|
||||||
|
}
|
||||||
|
|
||||||
const [ controlledDate, setControlledDate ] = useState(
|
const [ controlledDate, setControlledDate ] = useState(
|
||||||
new Date().toISOString()
|
nowWithZeroedSeconds().toISOString()
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -46,11 +55,19 @@ function ControlledContainer( { children, ...props } ) {
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
onClick={ () =>
|
onClick={ () =>
|
||||||
setControlledDate( new Date().toISOString() )
|
setControlledDate(
|
||||||
|
nowWithZeroedSeconds().toISOString()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Reset to now
|
Reset to now
|
||||||
</Button>
|
</Button>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
Controlled date:
|
||||||
|
<br /> <span>{ controlledDate }</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -68,25 +85,92 @@ CustomClassName.args = {
|
||||||
className: 'custom-class-name',
|
className: 'custom-class-name',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function ControlledDecorator( Story, props ) {
|
||||||
|
function nowWithZeroedSeconds() {
|
||||||
|
const now = new Date();
|
||||||
|
now.setSeconds( 0 );
|
||||||
|
now.setMilliseconds( 0 );
|
||||||
|
return now;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [ controlledDate, setControlledDate ] = useState(
|
||||||
|
nowWithZeroedSeconds().toISOString()
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Story
|
||||||
|
args={ {
|
||||||
|
...props.args,
|
||||||
|
currentDate: controlledDate,
|
||||||
|
onChange: setControlledDate,
|
||||||
|
} }
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
onClick={ () =>
|
||||||
|
setControlledDate(
|
||||||
|
nowWithZeroedSeconds().toISOString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Reset to now
|
||||||
|
</Button>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
Controlled date:
|
||||||
|
<br /> <span>{ controlledDate }</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const Controlled = Template.bind( {} );
|
export const Controlled = Template.bind( {} );
|
||||||
Controlled.args = {
|
Controlled.args = {
|
||||||
...Basic.args,
|
...Basic.args,
|
||||||
help: "I'm controlled by a container that uses React state",
|
help: "I'm controlled by a container that uses React state",
|
||||||
};
|
};
|
||||||
Controlled.decorators = [
|
Controlled.decorators = [ ControlledDecorator ];
|
||||||
( story, props ) => {
|
|
||||||
return (
|
export const ControlledDateOnly = Template.bind( {} );
|
||||||
<ControlledContainer>
|
ControlledDateOnly.args = {
|
||||||
{ ( controlledDate, setControlledDate ) =>
|
...Controlled.args,
|
||||||
story( {
|
isDateOnlyPicker: true,
|
||||||
args: {
|
};
|
||||||
currentDate: controlledDate,
|
ControlledDateOnly.decorators = Controlled.decorators;
|
||||||
onChange: setControlledDate,
|
|
||||||
|
export const ControlledDateOnlyEndOfDay = Template.bind( {} );
|
||||||
|
ControlledDateOnlyEndOfDay.args = {
|
||||||
|
...ControlledDateOnly.args,
|
||||||
|
timeForDateOnly: 'end-of-day',
|
||||||
|
};
|
||||||
|
ControlledDateOnlyEndOfDay.decorators = Controlled.decorators;
|
||||||
|
|
||||||
|
function PopoverSlotDecorator( Story, props ) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SlotFillProvider>
|
||||||
|
<div>
|
||||||
|
<Story
|
||||||
|
args={ {
|
||||||
...props.args,
|
...props.args,
|
||||||
},
|
} }
|
||||||
} )
|
/>
|
||||||
}
|
</div>
|
||||||
</ControlledContainer>
|
<Popover.Slot />
|
||||||
);
|
</SlotFillProvider>
|
||||||
},
|
</div>
|
||||||
];
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithPopoverSlot = Template.bind( {} );
|
||||||
|
WithPopoverSlot.args = {
|
||||||
|
...Basic.args,
|
||||||
|
label: 'Start date',
|
||||||
|
placeholder: 'Enter the start date',
|
||||||
|
help: 'There is a SlotFillProvider and Popover.Slot on the page',
|
||||||
|
isDateOnlyPicker: true,
|
||||||
|
};
|
||||||
|
WithPopoverSlot.decorators = [ PopoverSlotDecorator ];
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
*/
|
*/
|
||||||
import { render, waitFor, fireEvent } from '@testing-library/react';
|
import { render, waitFor, fireEvent } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import { format as formatDate } from '@wordpress/date';
|
||||||
import { createElement, Fragment } from '@wordpress/element';
|
import { createElement, Fragment } from '@wordpress/element';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
|
@ -96,13 +97,53 @@ describe( 'DateTimePickerControl', () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<DateTimePickerControl
|
<DateTimePickerControl
|
||||||
currentDate={ dateTime.toISOString() }
|
currentDate={ dateTime.toISOString() }
|
||||||
is12Hour={ false }
|
is12HourPicker={ false }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const input = container.querySelector( 'input' );
|
const input = container.querySelector( 'input' );
|
||||||
expect( input?.value ).toBe(
|
expect( input?.value ).toBe(
|
||||||
dateTime.format( default24HourDateTimeFormat )
|
formatDate( default24HourDateTimeFormat, dateTime )
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should assume ambiguous dates are UTC', () => {
|
||||||
|
const ambiguousISODateTimeString = '2202-09-15T22:30:40';
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<DateTimePickerControl
|
||||||
|
currentDate={ ambiguousISODateTimeString }
|
||||||
|
is12HourPicker={ false }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = container.querySelector( 'input' );
|
||||||
|
|
||||||
|
expect( input?.value ).toBe(
|
||||||
|
formatDate(
|
||||||
|
default24HourDateTimeFormat,
|
||||||
|
moment.utc( ambiguousISODateTimeString ).local()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should handle unambiguous UTC dates', () => {
|
||||||
|
const unambiguousISODateTimeString = '2202-09-15T22:30:40Z';
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<DateTimePickerControl
|
||||||
|
currentDate={ unambiguousISODateTimeString }
|
||||||
|
is12HourPicker={ false }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = container.querySelector( 'input' );
|
||||||
|
|
||||||
|
expect( input?.value ).toBe(
|
||||||
|
formatDate(
|
||||||
|
default24HourDateTimeFormat,
|
||||||
|
moment.utc( unambiguousISODateTimeString ).local()
|
||||||
|
)
|
||||||
);
|
);
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
@ -112,13 +153,13 @@ describe( 'DateTimePickerControl', () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<DateTimePickerControl
|
<DateTimePickerControl
|
||||||
currentDate={ dateTime.toISOString() }
|
currentDate={ dateTime.toISOString() }
|
||||||
is12Hour={ true }
|
is12HourPicker={ true }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const input = container.querySelector( 'input' );
|
const input = container.querySelector( 'input' );
|
||||||
expect( input?.value ).toBe(
|
expect( input?.value ).toBe(
|
||||||
dateTime.format( default12HourDateTimeFormat )
|
formatDate( default12HourDateTimeFormat, dateTime )
|
||||||
);
|
);
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
@ -134,7 +175,7 @@ describe( 'DateTimePickerControl', () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const input = container.querySelector( 'input' );
|
const input = container.querySelector( 'input' );
|
||||||
expect( input?.value ).toBe( dateTime.format( dateTimeFormat ) );
|
expect( input?.value ).toBe( formatDate( dateTimeFormat, dateTime ) );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
it( 'should update the input when currentDate is changed', () => {
|
it( 'should update the input when currentDate is changed', () => {
|
||||||
|
@ -144,20 +185,20 @@ describe( 'DateTimePickerControl', () => {
|
||||||
const { container, rerender } = render(
|
const { container, rerender } = render(
|
||||||
<DateTimePickerControl
|
<DateTimePickerControl
|
||||||
currentDate={ originalDateTime.toISOString() }
|
currentDate={ originalDateTime.toISOString() }
|
||||||
is12Hour={ false }
|
is12HourPicker={ false }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
rerender(
|
rerender(
|
||||||
<DateTimePickerControl
|
<DateTimePickerControl
|
||||||
currentDate={ updatedDateTime.toISOString() }
|
currentDate={ updatedDateTime.toISOString() }
|
||||||
is12Hour={ false }
|
is12HourPicker={ false }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const input = container.querySelector( 'input' );
|
const input = container.querySelector( 'input' );
|
||||||
expect( input?.value ).toBe(
|
expect( input?.value ).toBe(
|
||||||
updatedDateTime.format( default24HourDateTimeFormat )
|
formatDate( default24HourDateTimeFormat, updatedDateTime )
|
||||||
);
|
);
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
@ -189,9 +230,23 @@ describe( 'DateTimePickerControl', () => {
|
||||||
);
|
);
|
||||||
} );
|
} );
|
||||||
|
|
||||||
it( 'should set the date time picker popup to 12 hour mode', async () => {
|
it( 'should set the picker popup to date and time by default', async () => {
|
||||||
|
const { container } = render( <DateTimePickerControl /> );
|
||||||
|
|
||||||
|
const input = container.querySelector( 'input' );
|
||||||
|
|
||||||
|
userEvent.click( input! );
|
||||||
|
|
||||||
|
await waitFor( () =>
|
||||||
|
expect(
|
||||||
|
container.querySelector( '.components-datetime' )
|
||||||
|
).toBeInTheDocument()
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should set the picker to 12 hour mode', async () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<DateTimePickerControl is12Hour={ true } />
|
<DateTimePickerControl is12HourPicker={ true } />
|
||||||
);
|
);
|
||||||
|
|
||||||
const input = container.querySelector( 'input' );
|
const input = container.querySelector( 'input' );
|
||||||
|
@ -207,6 +262,25 @@ describe( 'DateTimePickerControl', () => {
|
||||||
);
|
);
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
it( 'should set the picker popup to date only', async () => {
|
||||||
|
const { container } = render(
|
||||||
|
<DateTimePickerControl isDateOnlyPicker={ true } />
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = container.querySelector( 'input' );
|
||||||
|
|
||||||
|
userEvent.click( input! );
|
||||||
|
|
||||||
|
await waitFor( () => {
|
||||||
|
expect(
|
||||||
|
container.querySelector( '.components-datetime' )
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
container.querySelector( '.components-datetime__date' )
|
||||||
|
).toBeInTheDocument();
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
it( 'should call onBlur when losing focus', async () => {
|
it( 'should call onBlur when losing focus', async () => {
|
||||||
const onBlurHandler = jest.fn();
|
const onBlurHandler = jest.fn();
|
||||||
|
|
||||||
|
@ -232,9 +306,9 @@ describe( 'DateTimePickerControl', () => {
|
||||||
// TypeError: Cannot read properties of null (reading 'createEvent')
|
// TypeError: Cannot read properties of null (reading 'createEvent')
|
||||||
it( 'should call onChange when the input is changed', async () => {
|
it( 'should call onChange when the input is changed', async () => {
|
||||||
const originalDateTime = moment( '2022-09-15 02:30:40' );
|
const originalDateTime = moment( '2022-09-15 02:30:40' );
|
||||||
const dateTimeFormat = 'HH:mm, MM-DD-YYYY';
|
const dateTimeFormat = 'm-d-Y, H:i';
|
||||||
const newDateTimeInputString = '02:04, 06-08-2010';
|
const newDateTimeInputString = '06-08-2010, 02:04';
|
||||||
const newDateTime = moment( newDateTimeInputString, dateTimeFormat );
|
const newDateTime = moment( newDateTimeInputString );
|
||||||
const onChangeHandler = jest.fn();
|
const onChangeHandler = jest.fn();
|
||||||
|
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
|
@ -262,6 +336,122 @@ describe( 'DateTimePickerControl', () => {
|
||||||
);
|
);
|
||||||
}, 10000 );
|
}, 10000 );
|
||||||
|
|
||||||
|
// We need to bump up the timeout for this test because:
|
||||||
|
// 1. userEvent.type() is slow (see https://github.com/testing-library/user-event/issues/577)
|
||||||
|
// 2. moment.js is slow
|
||||||
|
// Otherwise, the following error can occur on slow machines (such as our CI), because Jest times out and starts
|
||||||
|
// tearing down the component while test microtasks are still being executed
|
||||||
|
// (see https://github.com/facebook/jest/issues/12670)
|
||||||
|
// TypeError: Cannot read properties of null (reading 'createEvent')
|
||||||
|
it( 'should force time to the start of the day if date only', async () => {
|
||||||
|
const originalDateTime = moment( '09-15-2022' );
|
||||||
|
const newDateTimeInputString = '06-08-2010';
|
||||||
|
const newDateTime = moment( newDateTimeInputString ).startOf( 'day' );
|
||||||
|
const onChangeHandler = jest.fn();
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<DateTimePickerControl
|
||||||
|
isDateOnlyPicker
|
||||||
|
timeForDateOnly={ 'start-of-day' }
|
||||||
|
currentDate={ originalDateTime.toISOString() }
|
||||||
|
onChange={ onChangeHandler }
|
||||||
|
onChangeDebounceWait={ 10 }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = container.querySelector( 'input' );
|
||||||
|
userEvent.type(
|
||||||
|
input!,
|
||||||
|
'{selectall}{backspace}' + newDateTimeInputString
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(
|
||||||
|
() =>
|
||||||
|
expect( onChangeHandler ).toHaveBeenLastCalledWith(
|
||||||
|
newDateTime.toISOString(),
|
||||||
|
true
|
||||||
|
),
|
||||||
|
{ timeout: 100 }
|
||||||
|
);
|
||||||
|
}, 10000 );
|
||||||
|
|
||||||
|
// We need to bump up the timeout for this test because:
|
||||||
|
// 1. userEvent.type() is slow (see https://github.com/testing-library/user-event/issues/577)
|
||||||
|
// 2. moment.js is slow
|
||||||
|
// Otherwise, the following error can occur on slow machines (such as our CI), because Jest times out and starts
|
||||||
|
// tearing down the component while test microtasks are still being executed
|
||||||
|
// (see https://github.com/facebook/jest/issues/12670)
|
||||||
|
// TypeError: Cannot read properties of null (reading 'createEvent')
|
||||||
|
it( 'should force time to the end of the day if date only', async () => {
|
||||||
|
const originalDateTime = moment( '09-15-2022' );
|
||||||
|
const newDateTimeInputString = '06-08-2010';
|
||||||
|
const newDateTime = moment( newDateTimeInputString ).endOf( 'day' );
|
||||||
|
const onChangeHandler = jest.fn();
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<DateTimePickerControl
|
||||||
|
isDateOnlyPicker
|
||||||
|
timeForDateOnly={ 'end-of-day' }
|
||||||
|
currentDate={ originalDateTime.toISOString() }
|
||||||
|
onChange={ onChangeHandler }
|
||||||
|
onChangeDebounceWait={ 10 }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = container.querySelector( 'input' );
|
||||||
|
userEvent.type(
|
||||||
|
input!,
|
||||||
|
'{selectall}{backspace}' + newDateTimeInputString
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(
|
||||||
|
() =>
|
||||||
|
expect( onChangeHandler ).toHaveBeenLastCalledWith(
|
||||||
|
newDateTime.toISOString(),
|
||||||
|
true
|
||||||
|
),
|
||||||
|
{ timeout: 100 }
|
||||||
|
);
|
||||||
|
}, 10000 );
|
||||||
|
|
||||||
|
// We need to bump up the timeout for this test because:
|
||||||
|
// 1. userEvent.type() is slow (see https://github.com/testing-library/user-event/issues/577)
|
||||||
|
// 2. moment.js is slow
|
||||||
|
// Otherwise, the following error can occur on slow machines (such as our CI), because Jest times out and starts
|
||||||
|
// tearing down the component while test microtasks are still being executed
|
||||||
|
// (see https://github.com/facebook/jest/issues/12670)
|
||||||
|
// TypeError: Cannot read properties of null (reading 'createEvent')
|
||||||
|
it( 'should not force time to the start of the day if not date only', async () => {
|
||||||
|
const originalDateTime = moment( '09-15-2022' );
|
||||||
|
const newDateTimeInputString = '06-08-2010 7:00';
|
||||||
|
const newDateTime = moment( newDateTimeInputString );
|
||||||
|
const onChangeHandler = jest.fn();
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<DateTimePickerControl
|
||||||
|
timeForDateOnly={ 'start-of-day' }
|
||||||
|
currentDate={ originalDateTime.toISOString() }
|
||||||
|
onChange={ onChangeHandler }
|
||||||
|
onChangeDebounceWait={ 10 }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = container.querySelector( 'input' );
|
||||||
|
userEvent.type(
|
||||||
|
input!,
|
||||||
|
'{selectall}{backspace}' + newDateTimeInputString
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(
|
||||||
|
() =>
|
||||||
|
expect( onChangeHandler ).toHaveBeenLastCalledWith(
|
||||||
|
newDateTime.toISOString(),
|
||||||
|
true
|
||||||
|
),
|
||||||
|
{ timeout: 100 }
|
||||||
|
);
|
||||||
|
}, 10000 );
|
||||||
|
|
||||||
// We need to bump up the timeout for this test because:
|
// We need to bump up the timeout for this test because:
|
||||||
// 1. userEvent.type() is slow (see https://github.com/testing-library/user-event/issues/577)
|
// 1. userEvent.type() is slow (see https://github.com/testing-library/user-event/issues/577)
|
||||||
// 2. moment.js is slow
|
// 2. moment.js is slow
|
||||||
|
@ -304,9 +494,9 @@ describe( 'DateTimePickerControl', () => {
|
||||||
// TypeError: Cannot read properties of null (reading 'createEvent')
|
// TypeError: Cannot read properties of null (reading 'createEvent')
|
||||||
it( 'should call the current onChange when the input is changed', async () => {
|
it( 'should call the current onChange when the input is changed', async () => {
|
||||||
const originalDateTime = moment( '2022-09-15 02:30:40' );
|
const originalDateTime = moment( '2022-09-15 02:30:40' );
|
||||||
const dateTimeFormat = 'HH:mm, MM-DD-YYYY';
|
const dateTimeFormat = 'm-d-Y, H:i';
|
||||||
const newDateTimeInputString = '02:04, 06-08-2010';
|
const newDateTimeInputString = '06-08-2010, 02:04';
|
||||||
const newDateTime = moment( newDateTimeInputString, dateTimeFormat );
|
const newDateTime = moment( newDateTimeInputString );
|
||||||
const originalOnChangeHandler = jest.fn();
|
const originalOnChangeHandler = jest.fn();
|
||||||
const newOnChangeHandler = jest.fn();
|
const newOnChangeHandler = jest.fn();
|
||||||
|
|
||||||
|
@ -469,4 +659,112 @@ describe( 'DateTimePickerControl', () => {
|
||||||
|
|
||||||
await waitFor( () => expect( onChangeHandler ).not.toHaveBeenCalled() );
|
await waitFor( () => expect( onChangeHandler ).not.toHaveBeenCalled() );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
it( 'should not call onChange if currentDate is set to an equivalent UTC date without Zulu offset specifier', async () => {
|
||||||
|
const originalDateTime = '2023-01-01T00:00:00Z';
|
||||||
|
const equivalentDateTimeWithoutZulu = '2023-01-01T00:00:00';
|
||||||
|
const onChangeHandler = jest.fn();
|
||||||
|
|
||||||
|
const { rerender } = render(
|
||||||
|
<DateTimePickerControl
|
||||||
|
currentDate={ originalDateTime }
|
||||||
|
onChange={ onChangeHandler }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
// re-render the component; we do this to then test whether our onChange still gets called
|
||||||
|
rerender(
|
||||||
|
<DateTimePickerControl
|
||||||
|
currentDate={ equivalentDateTimeWithoutZulu }
|
||||||
|
onChange={ onChangeHandler }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor( () => expect( onChangeHandler ).not.toHaveBeenCalled() );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should not call onChange if currentDate is set to an equivalent UTC date without time', async () => {
|
||||||
|
const originalDateTime = '2023-01-01T00:00:00Z';
|
||||||
|
const equivalentDateTimeWithoutTime = '2023-01-01';
|
||||||
|
const onChangeHandler = jest.fn();
|
||||||
|
|
||||||
|
const { rerender } = render(
|
||||||
|
<DateTimePickerControl
|
||||||
|
currentDate={ originalDateTime }
|
||||||
|
onChange={ onChangeHandler }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
// re-render the component; we do this to then test whether our onChange still gets called
|
||||||
|
rerender(
|
||||||
|
<DateTimePickerControl
|
||||||
|
currentDate={ equivalentDateTimeWithoutTime }
|
||||||
|
onChange={ onChangeHandler }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor( () => expect( onChangeHandler ).not.toHaveBeenCalled() );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should not call onChange when the dateTimeFormat changes', async () => {
|
||||||
|
// we are specifically using a date with seconds in it, with a format
|
||||||
|
// without seconds in it; this helps us to determine if the currentDate
|
||||||
|
// is getting re-parsed from the input string (if it does this, it
|
||||||
|
// would result in a different date)
|
||||||
|
const originalDateTime = moment( '2022-11-15 02:30:40' );
|
||||||
|
const originalDateTimeFormat = 'm-d-Y, H:i';
|
||||||
|
const newDateTimeFormat = 'Y-m-d H:i';
|
||||||
|
const onChangeHandler = jest.fn();
|
||||||
|
|
||||||
|
const { rerender } = render(
|
||||||
|
<DateTimePickerControl
|
||||||
|
dateTimeFormat={ originalDateTimeFormat }
|
||||||
|
currentDate={ originalDateTime.toISOString() }
|
||||||
|
onChange={ onChangeHandler }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
// re-render the component; we do this to then test whether our onChange still gets called
|
||||||
|
rerender(
|
||||||
|
<DateTimePickerControl
|
||||||
|
dateTimeFormat={ newDateTimeFormat }
|
||||||
|
currentDate={ originalDateTime.toISOString() }
|
||||||
|
onChange={ onChangeHandler }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor( () => expect( onChangeHandler ).not.toHaveBeenCalled() );
|
||||||
|
} );
|
||||||
|
|
||||||
|
// We need to bump up the timeout for this test because:
|
||||||
|
// 1. userEvent.type() is slow (see https://github.com/testing-library/user-event/issues/577)
|
||||||
|
// 2. moment.js is slow
|
||||||
|
// Otherwise, the following error can occur on slow machines (such as our CI), because Jest times out and starts
|
||||||
|
// tearing down the component while test microtasks are still being executed
|
||||||
|
// (see https://github.com/facebook/jest/issues/12670)
|
||||||
|
// TypeError: Cannot read properties of null (reading 'createEvent')
|
||||||
|
it( 'should not call onChange when the input is changed to an equivalent date', async () => {
|
||||||
|
const originalDateTime = moment( '2022-09-15' );
|
||||||
|
const newDateTimeInputString = 'September 9, 2022';
|
||||||
|
const onChangeHandler = jest.fn();
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<DateTimePickerControl
|
||||||
|
isDateOnlyPicker={ true }
|
||||||
|
currentDate={ originalDateTime.toISOString() }
|
||||||
|
onChange={ onChangeHandler }
|
||||||
|
onChangeDebounceWait={ 5000 }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = container.querySelector( 'input' );
|
||||||
|
userEvent.type(
|
||||||
|
input!,
|
||||||
|
'{selectall}{backspace}' + newDateTimeInputString
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor( () => expect( onChangeHandler ).not.toHaveBeenCalled(), {
|
||||||
|
timeout: 10000,
|
||||||
|
} );
|
||||||
|
}, 10000 );
|
||||||
} );
|
} );
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
# EnrichedLabel
|
|
||||||
|
|
||||||
Use `EnrichedLabel` to create a label with a tooltip.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
<EnrichedLabel
|
|
||||||
label="My label"
|
|
||||||
helpDescription="My description."
|
|
||||||
moreUrl="https://woocommerce.com"
|
|
||||||
tooltipLinkCallback={ () => alert( 'Learn More clicked' ) }
|
|
||||||
/>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Props
|
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
|
||||||
| --------------------- | -------- | ------- | ----------------------------------------------------------------------- |
|
|
||||||
| `helpDescription` | String | `null` | Text that will be shown in the tooltip. |
|
|
||||||
| `label` | String | `null` | Text that will be shown in the label. |
|
|
||||||
| `moreUrl` | String | `null` | URL that will be added to the link `Learn More`, shown after the label. |
|
|
||||||
| `tooltipLinkCallback` | Function | `noop` | Callback that will be triggered after clicking the `Learn More` link. |
|
|
|
@ -1,75 +0,0 @@
|
||||||
/**
|
|
||||||
* External dependencies
|
|
||||||
*/
|
|
||||||
import { __ } from '@wordpress/i18n';
|
|
||||||
import { Button, Popover } from '@wordpress/components';
|
|
||||||
import { createElement, Fragment, useState } from '@wordpress/element';
|
|
||||||
import interpolateComponents from '@automattic/interpolate-components';
|
|
||||||
import { Icon, help } from '@wordpress/icons';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal dependencies
|
|
||||||
*/
|
|
||||||
import Link from '../link';
|
|
||||||
|
|
||||||
type EnrichedLabelProps = {
|
|
||||||
helpDescription: string;
|
|
||||||
label: string;
|
|
||||||
moreUrl: string;
|
|
||||||
tooltipLinkCallback: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const EnrichedLabel: React.FC< EnrichedLabelProps > = ( {
|
|
||||||
helpDescription,
|
|
||||||
label,
|
|
||||||
moreUrl,
|
|
||||||
tooltipLinkCallback,
|
|
||||||
} ) => {
|
|
||||||
const [ isPopoverVisible, setIsPopoverVisible ] = useState( false );
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<span className="woocommerce-enriched-label__text">{ label }</span>
|
|
||||||
{ helpDescription && (
|
|
||||||
<div
|
|
||||||
className="woocommerce-enriched-label__help-wrapper"
|
|
||||||
onMouseLeave={ () => setIsPopoverVisible( false ) }
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
label={ __( 'Help button', 'woocommerce' ) }
|
|
||||||
onMouseEnter={ () => setIsPopoverVisible( true ) }
|
|
||||||
>
|
|
||||||
<Icon icon={ help } />
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{ isPopoverVisible && (
|
|
||||||
<Popover focusOnMount="container" position="top center">
|
|
||||||
{ interpolateComponents( {
|
|
||||||
mixedString:
|
|
||||||
helpDescription +
|
|
||||||
( moreUrl ? ' {{moreLink/}}' : '' ),
|
|
||||||
components: {
|
|
||||||
moreLink: moreUrl ? (
|
|
||||||
<Link
|
|
||||||
href={ moreUrl }
|
|
||||||
target="_blank"
|
|
||||||
type="external"
|
|
||||||
onClick={ tooltipLinkCallback }
|
|
||||||
>
|
|
||||||
{ __(
|
|
||||||
'Learn more',
|
|
||||||
'woocommerce'
|
|
||||||
) }
|
|
||||||
</Link>
|
|
||||||
) : (
|
|
||||||
<div />
|
|
||||||
),
|
|
||||||
},
|
|
||||||
} ) }
|
|
||||||
</Popover>
|
|
||||||
) }
|
|
||||||
</div>
|
|
||||||
) }
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1 +0,0 @@
|
||||||
export * from './enriched-label';
|
|
|
@ -1,44 +0,0 @@
|
||||||
/**
|
|
||||||
* External dependencies
|
|
||||||
*/
|
|
||||||
import { CheckboxControl } from '@wordpress/components';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal dependencies
|
|
||||||
*/
|
|
||||||
import { EnrichedLabel } from '../';
|
|
||||||
import './style.scss';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'WooCommerce Admin/components/EnrichedLabel',
|
|
||||||
component: EnrichedLabel,
|
|
||||||
argTypes: {
|
|
||||||
tooltipLinkCallback: { action: 'tooltipLinkCallback' },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const Template = ( args ) => (
|
|
||||||
<EnrichedLabel
|
|
||||||
label="My label"
|
|
||||||
helpDescription="My description."
|
|
||||||
moreUrl="https://woocommerce.com"
|
|
||||||
tooltipLinkCallback={ () => {
|
|
||||||
// eslint-disable-next-line no-alert
|
|
||||||
window.alert( 'Learn More clicked' );
|
|
||||||
} }
|
|
||||||
{ ...args }
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const Basic = Template.bind( {} );
|
|
||||||
Basic.decorators = [
|
|
||||||
( story, props ) => {
|
|
||||||
return (
|
|
||||||
<CheckboxControl
|
|
||||||
className="woocommerce-enriched-label-story__checkbox-control"
|
|
||||||
label={ story( { args: { ...props.args } } ) }
|
|
||||||
onChange={ () => {} }
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
];
|
|
|
@ -1,23 +0,0 @@
|
||||||
.woocommerce-enriched-label-story__checkbox-control {
|
|
||||||
.woocommerce-enriched-label__help-wrapper {
|
|
||||||
.components-popover {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.components-base-control__field {
|
|
||||||
display: flex;
|
|
||||||
.components-checkbox-control {
|
|
||||||
&__label {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__input-container {
|
|
||||||
align-self: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.woocommerce-enriched-label__text {
|
|
||||||
align-self: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
.woocommerce-enriched-label__text {
|
|
||||||
align-self: center;
|
|
||||||
}
|
|
||||||
.woocommerce-enriched-label__help-wrapper {
|
|
||||||
.components-button {
|
|
||||||
padding: 0;
|
|
||||||
height: 28px;
|
|
||||||
}
|
|
||||||
.components-popover {
|
|
||||||
.components-popover__content {
|
|
||||||
min-width: 360px;
|
|
||||||
> div {
|
|
||||||
padding: $gap $gap-large;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,13 +5,25 @@
|
||||||
left: 0;
|
left: 0;
|
||||||
margin-top: $gap-smaller;
|
margin-top: $gap-smaller;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: none;
|
}
|
||||||
|
|
||||||
|
.components-popover.woocommerce-experimental-select-control__popover-menu {
|
||||||
background: $studio-white;
|
background: $studio-white;
|
||||||
border: 1px solid $studio-gray-5;
|
border: 1px solid $studio-gray-5;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
z-index: 10;
|
display: none;
|
||||||
|
|
||||||
&.is-open.has-results {
|
&.is-open.has-results {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.woocommerce-experimental-select-control__popover-menu-container {
|
||||||
|
margin: 0;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
|
||||||
|
> .category-field-dropdown__item:not( :first-child ) {
|
||||||
|
.category-field-dropdown__item-content {
|
||||||
|
border-top: 1px solid $gray-200;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,16 @@
|
||||||
/**
|
/**
|
||||||
* External dependencies
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
|
import { Popover } from '@wordpress/components';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { createElement } from '@wordpress/element';
|
import {
|
||||||
|
createElement,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
createPortal,
|
||||||
|
Children,
|
||||||
|
} from '@wordpress/element';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
|
@ -22,21 +30,65 @@ export const Menu = ( {
|
||||||
isOpen,
|
isOpen,
|
||||||
className,
|
className,
|
||||||
}: MenuProps ) => {
|
}: MenuProps ) => {
|
||||||
|
const [ boundingRect, setBoundingRect ] = useState< DOMRect >();
|
||||||
|
const selectControlMenuRef = useRef< HTMLDivElement >( null );
|
||||||
|
|
||||||
|
useEffect( () => {
|
||||||
|
if ( selectControlMenuRef.current?.parentElement ) {
|
||||||
|
setBoundingRect(
|
||||||
|
selectControlMenuRef.current.parentElement.getBoundingClientRect()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [ selectControlMenuRef.current ] );
|
||||||
|
|
||||||
|
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/click-events-have-key-events */
|
||||||
|
/* Disabled because of the onmouseup on the ul element below. */
|
||||||
return (
|
return (
|
||||||
<ul
|
<div
|
||||||
{ ...getMenuProps() }
|
ref={ selectControlMenuRef }
|
||||||
className={ classnames(
|
className={ classnames(
|
||||||
'woocommerce-experimental-select-control__menu',
|
'woocommerce-experimental-select-control__menu',
|
||||||
className,
|
className
|
||||||
{
|
|
||||||
'is-open': isOpen,
|
|
||||||
'has-results': Array.isArray( children )
|
|
||||||
? children.length
|
|
||||||
: Boolean( children ),
|
|
||||||
}
|
|
||||||
) }
|
) }
|
||||||
>
|
>
|
||||||
{ isOpen && children }
|
<Popover
|
||||||
</ul>
|
// @ts-expect-error this prop does exist, see: https://github.com/WordPress/gutenberg/blob/trunk/packages/components/src/popover/index.tsx#L180.
|
||||||
|
__unstableSlotName="woocommerce-select-control-menu"
|
||||||
|
focusOnMount={ false }
|
||||||
|
className={ classnames(
|
||||||
|
'woocommerce-experimental-select-control__popover-menu',
|
||||||
|
{
|
||||||
|
'is-open': isOpen,
|
||||||
|
'has-results': Children.count( children ) > 0,
|
||||||
|
}
|
||||||
|
) }
|
||||||
|
position="bottom center"
|
||||||
|
animate={ false }
|
||||||
|
>
|
||||||
|
<ul
|
||||||
|
{ ...getMenuProps() }
|
||||||
|
className="woocommerce-experimental-select-control__popover-menu-container"
|
||||||
|
style={ {
|
||||||
|
width: boundingRect?.width,
|
||||||
|
} }
|
||||||
|
onMouseUp={ ( e ) =>
|
||||||
|
// Fix to prevent select control dropdown from closing when selecting within the Popover.
|
||||||
|
e.stopPropagation()
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{ isOpen && children }
|
||||||
|
</ul>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
|
/* eslint-enable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/click-events-have-key-events */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const MenuSlot: React.FC = () =>
|
||||||
|
createPortal(
|
||||||
|
<div aria-live="off">
|
||||||
|
{ /* @ts-expect-error name does exist on PopoverSlot see: https://github.com/WordPress/gutenberg/blob/trunk/packages/components/src/popover/index.tsx#L555 */ }
|
||||||
|
<Popover.Slot name="woocommerce-select-control-menu" />
|
||||||
|
</div>,
|
||||||
|
document.body
|
||||||
|
);
|
||||||
|
|
|
@ -48,7 +48,10 @@ type SelectControlProps< ItemType > = {
|
||||||
) => ItemType[];
|
) => ItemType[];
|
||||||
hasExternalTags?: boolean;
|
hasExternalTags?: boolean;
|
||||||
multiple?: boolean;
|
multiple?: boolean;
|
||||||
onInputChange?: ( value: string | undefined ) => void;
|
onInputChange?: (
|
||||||
|
value: string | undefined,
|
||||||
|
changes: Partial< Omit< UseComboboxState< ItemType >, 'inputValue' > >
|
||||||
|
) => void;
|
||||||
onRemove?: ( item: ItemType ) => void;
|
onRemove?: ( item: ItemType ) => void;
|
||||||
onSelect?: ( selected: ItemType ) => void;
|
onSelect?: ( selected: ItemType ) => void;
|
||||||
onFocus?: ( data: { inputValue: string } ) => void;
|
onFocus?: ( data: { inputValue: string } ) => void;
|
||||||
|
@ -59,6 +62,7 @@ type SelectControlProps< ItemType > = {
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
selected: ItemType | ItemType[] | null;
|
selected: ItemType | ItemType[] | null;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const selectControlStateChangeTypes = useCombobox.stateChangeTypes;
|
export const selectControlStateChangeTypes = useCombobox.stateChangeTypes;
|
||||||
|
@ -102,6 +106,7 @@ function SelectControl< ItemType = DefaultItemType >( {
|
||||||
placeholder,
|
placeholder,
|
||||||
selected,
|
selected,
|
||||||
className,
|
className,
|
||||||
|
disabled,
|
||||||
}: SelectControlProps< ItemType > ) {
|
}: SelectControlProps< ItemType > ) {
|
||||||
const [ isFocused, setIsFocused ] = useState( false );
|
const [ isFocused, setIsFocused ] = useState( false );
|
||||||
const [ inputValue, setInputValue ] = useState( '' );
|
const [ inputValue, setInputValue ] = useState( '' );
|
||||||
|
@ -150,16 +155,14 @@ function SelectControl< ItemType = DefaultItemType >( {
|
||||||
initialSelectedItem: singleSelectedItem,
|
initialSelectedItem: singleSelectedItem,
|
||||||
inputValue,
|
inputValue,
|
||||||
items: filteredItems,
|
items: filteredItems,
|
||||||
selectedItem: multiple ? null : undefined,
|
selectedItem: multiple ? null : singleSelectedItem,
|
||||||
itemToString: getItemLabel,
|
itemToString: getItemLabel,
|
||||||
onSelectedItemChange: ( { selectedItem } ) =>
|
onSelectedItemChange: ( { selectedItem } ) =>
|
||||||
selectedItem && onSelect( selectedItem ),
|
selectedItem && onSelect( selectedItem ),
|
||||||
onInputValueChange: ( changes ) => {
|
onInputValueChange: ( { inputValue: value, ...changes } ) => {
|
||||||
if ( changes.inputValue !== undefined ) {
|
if ( value !== undefined ) {
|
||||||
setInputValue( changes.inputValue );
|
setInputValue( value );
|
||||||
if ( changes.isOpen ) {
|
onInputChange( value, changes );
|
||||||
onInputChange( changes.inputValue );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
stateReducer: ( state, actionAndChanges ) => {
|
stateReducer: ( state, actionAndChanges ) => {
|
||||||
|
@ -225,12 +228,14 @@ function SelectControl< ItemType = DefaultItemType >( {
|
||||||
>
|
>
|
||||||
{ /* Downshift's getLabelProps handles the necessary label attributes. */ }
|
{ /* Downshift's getLabelProps handles the necessary label attributes. */ }
|
||||||
{ /* eslint-disable jsx-a11y/label-has-for */ }
|
{ /* eslint-disable jsx-a11y/label-has-for */ }
|
||||||
<label
|
{ label && (
|
||||||
{ ...getLabelProps() }
|
<label
|
||||||
className="woocommerce-experimental-select-control__label"
|
{ ...getLabelProps() }
|
||||||
>
|
className="woocommerce-experimental-select-control__label"
|
||||||
{ label }
|
>
|
||||||
</label>
|
{ label }
|
||||||
|
</label>
|
||||||
|
) }
|
||||||
{ /* eslint-enable jsx-a11y/label-has-for */ }
|
{ /* eslint-enable jsx-a11y/label-has-for */ }
|
||||||
<ComboBox
|
<ComboBox
|
||||||
comboBoxProps={ getComboboxProps() }
|
comboBoxProps={ getComboboxProps() }
|
||||||
|
@ -245,6 +250,7 @@ function SelectControl< ItemType = DefaultItemType >( {
|
||||||
},
|
},
|
||||||
onBlur: () => setIsFocused( false ),
|
onBlur: () => setIsFocused( false ),
|
||||||
placeholder,
|
placeholder,
|
||||||
|
disabled,
|
||||||
} ) }
|
} ) }
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
/**
|
/**
|
||||||
* External dependencies
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
import { CheckboxControl, Spinner } from '@wordpress/components';
|
import {
|
||||||
|
Button,
|
||||||
|
CheckboxControl,
|
||||||
|
Modal,
|
||||||
|
SlotFillProvider,
|
||||||
|
Spinner,
|
||||||
|
} from '@wordpress/components';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { createElement, useState } from '@wordpress/element';
|
import { createElement, useState } from '@wordpress/element';
|
||||||
|
|
||||||
|
@ -11,7 +17,7 @@ import { createElement, useState } from '@wordpress/element';
|
||||||
import { SelectedType, DefaultItemType, getItemLabelType } from '../types';
|
import { SelectedType, DefaultItemType, getItemLabelType } from '../types';
|
||||||
import { MenuItem } from '../menu-item';
|
import { MenuItem } from '../menu-item';
|
||||||
import { SelectControl, selectControlStateChangeTypes } from '../';
|
import { SelectControl, selectControlStateChangeTypes } from '../';
|
||||||
import { Menu } from '../menu';
|
import { Menu, MenuSlot } from '../menu';
|
||||||
|
|
||||||
const sampleItems = [
|
const sampleItems = [
|
||||||
{ value: 'apple', label: 'Apple' },
|
{ value: 'apple', label: 'Apple' },
|
||||||
|
@ -365,6 +371,45 @@ export const CustomItemType: React.FC = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const SingleWithinModalUsingBodyDropdownPlacement: React.FC = () => {
|
||||||
|
const [ isOpen, setOpen ] = useState( true );
|
||||||
|
const [ selected, setSelected ] =
|
||||||
|
useState< SelectedType< DefaultItemType > >();
|
||||||
|
const [ selectedTwo, setSelectedTwo ] =
|
||||||
|
useState< SelectedType< DefaultItemType > >();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SlotFillProvider>
|
||||||
|
Selected: { JSON.stringify( selected ) }
|
||||||
|
<Button onClick={ () => setOpen( true ) }>
|
||||||
|
Show Dropdown in Modal
|
||||||
|
</Button>
|
||||||
|
{ isOpen && (
|
||||||
|
<Modal
|
||||||
|
title="Dropdown Modal"
|
||||||
|
onRequestClose={ () => setOpen( false ) }
|
||||||
|
>
|
||||||
|
<SelectControl
|
||||||
|
items={ sampleItems }
|
||||||
|
label="Single value"
|
||||||
|
selected={ selected }
|
||||||
|
onSelect={ ( item ) => item && setSelected( item ) }
|
||||||
|
onRemove={ () => setSelected( null ) }
|
||||||
|
/>
|
||||||
|
<SelectControl
|
||||||
|
items={ sampleItems }
|
||||||
|
label="Single value"
|
||||||
|
selected={ selectedTwo }
|
||||||
|
onSelect={ ( item ) => item && setSelectedTwo( item ) }
|
||||||
|
onRemove={ () => setSelectedTwo( null ) }
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
) }
|
||||||
|
<MenuSlot />
|
||||||
|
</SlotFillProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'WooCommerce Admin/experimental/SelectControl',
|
title: 'WooCommerce Admin/experimental/SelectControl',
|
||||||
component: SelectControl,
|
component: SelectControl,
|
||||||
|
|
|
@ -1,9 +1,18 @@
|
||||||
/**
|
/**
|
||||||
* External dependencies
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
import { ChangeEvent } from 'react';
|
|
||||||
import { createContext, useContext } from '@wordpress/element';
|
import { createContext, useContext } from '@wordpress/element';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
CheckboxProps,
|
||||||
|
ConsumerInputProps,
|
||||||
|
InputProps,
|
||||||
|
SelectControlProps,
|
||||||
|
} from './form';
|
||||||
|
|
||||||
export type FormErrors< Values > = {
|
export type FormErrors< Values > = {
|
||||||
[ P in keyof Values ]?: FormErrors< Values[ P ] > | string;
|
[ P in keyof Values ]?: FormErrors< Values[ P ] > | string;
|
||||||
};
|
};
|
||||||
|
@ -21,17 +30,18 @@ export type FormContext< Values extends Record< string, any > > = {
|
||||||
setValue: ( name: string, value: any ) => void;
|
setValue: ( name: string, value: any ) => void;
|
||||||
setValues: ( valuesToSet: Values ) => void;
|
setValues: ( valuesToSet: Values ) => void;
|
||||||
handleSubmit: () => Promise< Values >;
|
handleSubmit: () => Promise< Values >;
|
||||||
|
getCheckboxControlProps< Value extends Values[ keyof Values ] >(
|
||||||
|
name: string,
|
||||||
|
inputProps?: ConsumerInputProps< Values >
|
||||||
|
): CheckboxProps< Values, Value >;
|
||||||
|
getSelectControlProps< Value extends Values[ keyof Values ] >(
|
||||||
|
name: string,
|
||||||
|
inputProps?: ConsumerInputProps< Values >
|
||||||
|
): SelectControlProps< Values, Value >;
|
||||||
getInputProps< Value extends Values[ keyof Values ] >(
|
getInputProps< Value extends Values[ keyof Values ] >(
|
||||||
name: string
|
name: string,
|
||||||
): {
|
inputProps?: ConsumerInputProps< Values >
|
||||||
value: Value;
|
): InputProps< Values, Value >;
|
||||||
checked: boolean;
|
|
||||||
selected?: boolean;
|
|
||||||
onChange: ( value: ChangeEvent< HTMLInputElement > | Value ) => void;
|
|
||||||
onBlur: () => void;
|
|
||||||
className: string | undefined;
|
|
||||||
help: string | null | undefined;
|
|
||||||
};
|
|
||||||
isValidForm: boolean;
|
isValidForm: boolean;
|
||||||
resetForm: (
|
resetForm: (
|
||||||
initialValues: Values,
|
initialValues: Values,
|
||||||
|
@ -48,7 +58,7 @@ export const FormContext = createContext< FormContext< any > >(
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export function useFormContext< Values extends Record< string, any > >() {
|
export function useFormContext< Values extends Record< string, any > >() {
|
||||||
const formik = useContext< FormContext< Values > >( FormContext );
|
const formContext = useContext< FormContext< Values > >( FormContext );
|
||||||
|
|
||||||
return formik;
|
return formContext;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* External dependencies
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
|
import classnames from 'classnames';
|
||||||
import {
|
import {
|
||||||
cloneElement,
|
cloneElement,
|
||||||
useState,
|
useState,
|
||||||
|
@ -17,6 +18,7 @@ import _setWith from 'lodash/setWith';
|
||||||
import _get from 'lodash/get';
|
import _get from 'lodash/get';
|
||||||
import _clone from 'lodash/clone';
|
import _clone from 'lodash/clone';
|
||||||
import _isEqual from 'lodash/isEqual';
|
import _isEqual from 'lodash/isEqual';
|
||||||
|
import _omit from 'lodash/omit';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
|
@ -87,6 +89,40 @@ export type FormRef< Values > = {
|
||||||
resetForm: ( initialValues: Values ) => void;
|
resetForm: ( initialValues: Values ) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type InputProps< Values, Value > = {
|
||||||
|
value: Value;
|
||||||
|
checked: boolean;
|
||||||
|
selected?: boolean;
|
||||||
|
onChange: (
|
||||||
|
value: ChangeEvent< HTMLInputElement > | Values[ keyof Values ]
|
||||||
|
) => void;
|
||||||
|
onBlur: () => void;
|
||||||
|
className: string | undefined;
|
||||||
|
help: string | null | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CheckboxProps< Values, Value > = Omit<
|
||||||
|
InputProps< Values, Value >,
|
||||||
|
'value' | 'selected'
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type SelectControlProps< Values, Value > = Omit<
|
||||||
|
InputProps< Values, Value >,
|
||||||
|
'value'
|
||||||
|
> & {
|
||||||
|
value: string | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ConsumerInputProps< Values > = {
|
||||||
|
className?: string;
|
||||||
|
onChange?: (
|
||||||
|
value: ChangeEvent< HTMLInputElement > | Values[ keyof Values ]
|
||||||
|
) => void;
|
||||||
|
onBlur?: () => void;
|
||||||
|
[ key: string ]: unknown;
|
||||||
|
sanitize?: ( value: Values[ keyof Values ] ) => Values[ keyof Values ];
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A form component to handle form state and provide input helper props.
|
* A form component to handle form state and provide input helper props.
|
||||||
*/
|
*/
|
||||||
|
@ -268,21 +304,19 @@ function FormComponent< Values extends Record< string, any > >(
|
||||||
};
|
};
|
||||||
|
|
||||||
function getInputProps< Value = Values[ keyof Values ] >(
|
function getInputProps< Value = Values[ keyof Values ] >(
|
||||||
name: string
|
name: string,
|
||||||
): {
|
inputProps: ConsumerInputProps< Values > = {}
|
||||||
value: Value;
|
): InputProps< Values, Value > {
|
||||||
checked: boolean;
|
|
||||||
selected?: boolean;
|
|
||||||
onChange: (
|
|
||||||
value: ChangeEvent< HTMLInputElement > | Values[ keyof Values ]
|
|
||||||
) => void;
|
|
||||||
onBlur: () => void;
|
|
||||||
className: string | undefined;
|
|
||||||
help: string | null | undefined;
|
|
||||||
} {
|
|
||||||
const inputValue = _get( values, name );
|
const inputValue = _get( values, name );
|
||||||
const isTouched = touched[ name ];
|
const isTouched = touched[ name ];
|
||||||
const inputError = _get( errors, name );
|
const inputError = _get( errors, name );
|
||||||
|
const {
|
||||||
|
className: classNameProp,
|
||||||
|
onBlur: onBlurProp,
|
||||||
|
onChange: onChangeProp,
|
||||||
|
sanitize,
|
||||||
|
...additionalProps
|
||||||
|
} = inputProps;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
value: inputValue,
|
value: inputValue,
|
||||||
|
@ -290,10 +324,50 @@ function FormComponent< Values extends Record< string, any > >(
|
||||||
selected: inputValue,
|
selected: inputValue,
|
||||||
onChange: (
|
onChange: (
|
||||||
value: ChangeEvent< HTMLInputElement > | Values[ keyof Values ]
|
value: ChangeEvent< HTMLInputElement > | Values[ keyof Values ]
|
||||||
) => handleChange( name, value ),
|
) => {
|
||||||
onBlur: () => handleBlur( name ),
|
handleChange( name, value );
|
||||||
className: isTouched && inputError ? 'has-error' : undefined,
|
if ( onChangeProp ) {
|
||||||
|
onChangeProp( value );
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onBlur: () => {
|
||||||
|
if ( sanitize ) {
|
||||||
|
handleChange( name, sanitize( inputValue ) );
|
||||||
|
}
|
||||||
|
handleBlur( name );
|
||||||
|
if ( onBlurProp ) {
|
||||||
|
onBlurProp();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
className: classnames( classNameProp, {
|
||||||
|
'has-error': isTouched && inputError,
|
||||||
|
} ),
|
||||||
help: isTouched ? ( inputError as string ) : null,
|
help: isTouched ? ( inputError as string ) : null,
|
||||||
|
...additionalProps,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCheckboxControlProps< Value = Values[ keyof Values ] >(
|
||||||
|
name: string,
|
||||||
|
inputProps: ConsumerInputProps< Values > = {}
|
||||||
|
): CheckboxProps< Values, Value > {
|
||||||
|
return _omit( getInputProps( name, inputProps ), [
|
||||||
|
'selected',
|
||||||
|
'value',
|
||||||
|
] );
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSelectControlProps< Value = Values[ keyof Values ] >(
|
||||||
|
name: string,
|
||||||
|
inputProps: ConsumerInputProps< Values > = {}
|
||||||
|
): SelectControlProps< Values, Value > {
|
||||||
|
const selectControlProps = getInputProps( name, inputProps );
|
||||||
|
return {
|
||||||
|
...selectControlProps,
|
||||||
|
value:
|
||||||
|
selectControlProps.value === undefined
|
||||||
|
? undefined
|
||||||
|
: String( selectControlProps.value ),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -312,7 +386,9 @@ function FormComponent< Values extends Record< string, any > >(
|
||||||
setValue,
|
setValue,
|
||||||
setValues,
|
setValues,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
|
getCheckboxControlProps,
|
||||||
getInputProps,
|
getInputProps,
|
||||||
|
getSelectControlProps,
|
||||||
isValidForm: ! Object.keys( errors ).length,
|
isValidForm: ! Object.keys( errors ).length,
|
||||||
resetForm,
|
resetForm,
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,7 +9,8 @@ import {
|
||||||
TextControl,
|
TextControl,
|
||||||
} from '@wordpress/components';
|
} from '@wordpress/components';
|
||||||
import { useState } from '@wordpress/element';
|
import { useState } from '@wordpress/element';
|
||||||
import { Form } from '@woocommerce/components';
|
import { Form, DateTimePickerControl } from '@woocommerce/components';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
const validate = ( values ) => {
|
const validate = ( values ) => {
|
||||||
const errors = {};
|
const errors = {};
|
||||||
|
@ -19,6 +20,9 @@ const validate = ( values ) => {
|
||||||
if ( values.lastName.length < 3 ) {
|
if ( values.lastName.length < 3 ) {
|
||||||
errors.lastName = 'Last name must be at least 3 characters';
|
errors.lastName = 'Last name must be at least 3 characters';
|
||||||
}
|
}
|
||||||
|
if ( ! moment( values.date, moment.ISO_8601, true ).isValid() ) {
|
||||||
|
errors.date = 'Invalid date';
|
||||||
|
}
|
||||||
return errors;
|
return errors;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -28,6 +32,7 @@ const initialValues = {
|
||||||
firstName: '',
|
firstName: '',
|
||||||
lastName: '',
|
lastName: '',
|
||||||
select: '3',
|
select: '3',
|
||||||
|
date: '2014-10-24T13:02',
|
||||||
checkbox: true,
|
checkbox: true,
|
||||||
radio: 'one',
|
radio: 'one',
|
||||||
};
|
};
|
||||||
|
@ -68,6 +73,13 @@ export const Basic = () => {
|
||||||
] }
|
] }
|
||||||
{ ...getInputProps( 'select' ) }
|
{ ...getInputProps( 'select' ) }
|
||||||
/>
|
/>
|
||||||
|
<DateTimePickerControl
|
||||||
|
label="Date"
|
||||||
|
dateTimeFormat="YYYY-MM-DD HH:mm"
|
||||||
|
placeholder="Enter a date"
|
||||||
|
currentDate={ values.date }
|
||||||
|
{ ...getInputProps( 'date' ) }
|
||||||
|
/>
|
||||||
<CheckboxControl
|
<CheckboxControl
|
||||||
label="Checkbox"
|
label="Checkbox"
|
||||||
{ ...getInputProps( 'checkbox' ) }
|
{ ...getInputProps( 'checkbox' ) }
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { TextControl } from '@wordpress/components';
|
||||||
*/
|
*/
|
||||||
import { Form, useFormContext } from '../';
|
import { Form, useFormContext } from '../';
|
||||||
import type { FormContext } from '../';
|
import type { FormContext } from '../';
|
||||||
|
import { DateTimePickerControl } from '../../date-time-picker-control';
|
||||||
|
|
||||||
const TestInputWithContext = () => {
|
const TestInputWithContext = () => {
|
||||||
const formProps = useFormContext< { foo: string } >();
|
const formProps = useFormContext< { foo: string } >();
|
||||||
|
@ -407,6 +408,68 @@ describe( 'Form', () => {
|
||||||
);
|
);
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
// We need to bump up the timeout for this test because:
|
||||||
|
// 1. userEvent.type() is slow (see https://github.com/testing-library/user-event/issues/577)
|
||||||
|
// 2. moment.js is slow
|
||||||
|
// Otherwise, the following error can occur on slow machines (such as our CI), because Jest times out and starts
|
||||||
|
// tearing down the component while test microtasks are still being executed
|
||||||
|
// (see https://github.com/facebook/jest/issues/12670)
|
||||||
|
// TypeError: Cannot read properties of null (reading 'createEvent')
|
||||||
|
it( 'should provide props that automatically handle DateTimePickerControl changes', async () => {
|
||||||
|
const newDateTimeInputString = 'invalid input';
|
||||||
|
|
||||||
|
type TestData = { date: string };
|
||||||
|
|
||||||
|
const mockOnChange = jest.fn();
|
||||||
|
|
||||||
|
function validate(): Record< string, string > {
|
||||||
|
return { date: 'This is a bad date' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const { container, queryByText } = render(
|
||||||
|
<Form< TestData > onChange={ mockOnChange } validate={ validate }>
|
||||||
|
{ ( { getInputProps, values }: FormContext< TestData > ) => {
|
||||||
|
return (
|
||||||
|
<DateTimePickerControl
|
||||||
|
label={ 'Date' }
|
||||||
|
onChangeDebounceWait={ 10 }
|
||||||
|
currentDate={ values.date }
|
||||||
|
{ ...getInputProps( 'date' ) }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} }
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
|
||||||
|
const controlRoot = container.querySelector(
|
||||||
|
'.woocommerce-date-time-picker-control'
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = controlRoot?.querySelector( 'input' );
|
||||||
|
userEvent.type(
|
||||||
|
input!,
|
||||||
|
'{selectall}{backspace}' + newDateTimeInputString
|
||||||
|
);
|
||||||
|
fireEvent.blur( input! );
|
||||||
|
|
||||||
|
await waitFor(
|
||||||
|
() => {
|
||||||
|
expect( mockOnChange ).toHaveBeenLastCalledWith(
|
||||||
|
{ name: 'date', value: newDateTimeInputString },
|
||||||
|
{ date: newDateTimeInputString },
|
||||||
|
false
|
||||||
|
);
|
||||||
|
expect( controlRoot?.classList.contains( 'has-error' ) ).toBe(
|
||||||
|
true
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
queryByText( 'This is a bad date' )
|
||||||
|
).toBeInTheDocument();
|
||||||
|
},
|
||||||
|
{ timeout: 100 }
|
||||||
|
);
|
||||||
|
}, 10000 );
|
||||||
|
|
||||||
describe( 'FormContext', () => {
|
describe( 'FormContext', () => {
|
||||||
it( 'should allow nested field to use useFormContext to set field value', async () => {
|
it( 'should allow nested field to use useFormContext to set field value', async () => {
|
||||||
const mockOnChange = jest.fn();
|
const mockOnChange = jest.fn();
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { createElement, Fragment } from '@wordpress/element';
|
||||||
*/
|
*/
|
||||||
import Pill from '../pill';
|
import Pill from '../pill';
|
||||||
import { SortableHandle, NonSortableItem } from '../sortable';
|
import { SortableHandle, NonSortableItem } from '../sortable';
|
||||||
import { ConditionalWrapper } from '../util/conditional-wrapper';
|
import { ConditionalWrapper } from '../conditional-wrapper';
|
||||||
|
|
||||||
export type ImageGalleryItemProps = {
|
export type ImageGalleryItemProps = {
|
||||||
id?: string;
|
id?: string;
|
||||||
|
|
|
@ -4,6 +4,7 @@ export { default as AnimationSlider } from './animation-slider';
|
||||||
export { default as Chart } from './chart';
|
export { default as Chart } from './chart';
|
||||||
export { default as ChartPlaceholder } from './chart/placeholder';
|
export { default as ChartPlaceholder } from './chart/placeholder';
|
||||||
export { CompareButton, CompareFilter } from './compare-filter';
|
export { CompareButton, CompareFilter } from './compare-filter';
|
||||||
|
export { ConditionalWrapper as __experimentalConditionalWrapper } from './conditional-wrapper';
|
||||||
export { default as Date } from './date';
|
export { default as Date } from './date';
|
||||||
export { default as DateRangeFilterPicker } from './date-range-filter-picker';
|
export { default as DateRangeFilterPicker } from './date-range-filter-picker';
|
||||||
export { default as DateRange } from './calendar/date-range';
|
export { default as DateRange } from './calendar/date-range';
|
||||||
|
@ -49,7 +50,10 @@ export {
|
||||||
MenuItem as __experimentalSelectControlMenuItem,
|
MenuItem as __experimentalSelectControlMenuItem,
|
||||||
MenuItemProps as __experimentalSelectControlMenuItemProps,
|
MenuItemProps as __experimentalSelectControlMenuItemProps,
|
||||||
} from './experimental-select-control/menu-item';
|
} from './experimental-select-control/menu-item';
|
||||||
export { Menu as __experimentalSelectControlMenu } from './experimental-select-control/menu';
|
export {
|
||||||
|
Menu as __experimentalSelectControlMenu,
|
||||||
|
MenuSlot as __experimentalSelectControlMenuSlot,
|
||||||
|
} from './experimental-select-control/menu';
|
||||||
export { default as ScrollTo } from './scroll-to';
|
export { default as ScrollTo } from './scroll-to';
|
||||||
export { Sortable } from './sortable';
|
export { Sortable } from './sortable';
|
||||||
export { ListItem } from './list-item';
|
export { ListItem } from './list-item';
|
||||||
|
@ -71,11 +75,11 @@ export { default as Tag } from './tag';
|
||||||
export { default as TextControl } from './text-control';
|
export { default as TextControl } from './text-control';
|
||||||
export { default as TextControlWithAffixes } from './text-control-with-affixes';
|
export { default as TextControlWithAffixes } from './text-control-with-affixes';
|
||||||
export { default as Timeline } from './timeline';
|
export { default as Timeline } from './timeline';
|
||||||
|
export { Tooltip as __experimentalTooltip } from './tooltip';
|
||||||
export { default as ViewMoreList } from './view-more-list';
|
export { default as ViewMoreList } from './view-more-list';
|
||||||
export { default as WebPreview } from './web-preview';
|
export { default as WebPreview } from './web-preview';
|
||||||
export { Badge } from './badge';
|
export { Badge } from './badge';
|
||||||
export { DynamicForm } from './dynamic-form';
|
export { DynamicForm } from './dynamic-form';
|
||||||
export { EnrichedLabel } from './enriched-label';
|
|
||||||
export { default as TourKit } from './tour-kit';
|
export { default as TourKit } from './tour-kit';
|
||||||
export * as TourKitTypes from './tour-kit/types';
|
export * as TourKitTypes from './tour-kit/types';
|
||||||
export { CollapsibleContent } from './collapsible-content';
|
export { CollapsibleContent } from './collapsible-content';
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
*/
|
*/
|
||||||
import { useSelect, useDispatch } from '@wordpress/data';
|
import { useSelect, useDispatch } from '@wordpress/data';
|
||||||
import { useInstanceId } from '@wordpress/compose';
|
import { useInstanceId } from '@wordpress/compose';
|
||||||
|
import { BlockInstance, createBlock } from '@wordpress/blocks';
|
||||||
import { createElement, useEffect } from '@wordpress/element';
|
import { createElement, useEffect } from '@wordpress/element';
|
||||||
import { createBlock } from '@wordpress/blocks';
|
|
||||||
import {
|
import {
|
||||||
BlockList,
|
BlockList,
|
||||||
ObserveTyping,
|
ObserveTyping,
|
||||||
|
@ -15,37 +15,50 @@ import {
|
||||||
WritingFlow,
|
WritingFlow,
|
||||||
} from '@wordpress/block-editor';
|
} from '@wordpress/block-editor';
|
||||||
|
|
||||||
export const EditorWritingFlow: React.VFC = () => {
|
type EditorWritingFlowProps = {
|
||||||
|
blocks: BlockInstance[];
|
||||||
|
onChange: ( changes: BlockInstance[] ) => void;
|
||||||
|
placeholder?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EditorWritingFlow = ( {
|
||||||
|
blocks,
|
||||||
|
onChange,
|
||||||
|
placeholder = '',
|
||||||
|
}: EditorWritingFlowProps ) => {
|
||||||
const instanceId = useInstanceId( EditorWritingFlow );
|
const instanceId = useInstanceId( EditorWritingFlow );
|
||||||
|
const firstBlock = blocks[ 0 ];
|
||||||
|
const isEmpty = ! blocks.length;
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore This action is available in the block editor data store.
|
// @ts-ignore This action is available in the block editor data store.
|
||||||
const { insertBlock } = useDispatch( blockEditorStore );
|
const { insertBlock, selectBlock } = useDispatch( blockEditorStore );
|
||||||
|
const { selectedBlockClientIds } = useSelect( ( select ) => {
|
||||||
const { isEmpty } = useSelect( ( select ) => {
|
|
||||||
const blocks = select( 'core/block-editor' ).getBlocks();
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore This selector is available in the block editor data store.
|
// @ts-ignore This selector is available in the block editor data store.
|
||||||
const { getSelectedBlockClientIds } = select( blockEditorStore );
|
const { getSelectedBlockClientIds } = select( blockEditorStore );
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isEmpty: blocks.length
|
|
||||||
? blocks.length <= 1 &&
|
|
||||||
blocks[ 0 ].attributes?.content?.trim() === ''
|
|
||||||
: true,
|
|
||||||
firstBlock: blocks[ 0 ],
|
|
||||||
selectedBlockClientIds: getSelectedBlockClientIds(),
|
selectedBlockClientIds: getSelectedBlockClientIds(),
|
||||||
};
|
};
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
useEffect( () => {
|
||||||
|
if ( selectedBlockClientIds?.length || ! firstBlock ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
selectBlock( firstBlock.clientId );
|
||||||
|
}, [ firstBlock, selectedBlockClientIds ] );
|
||||||
|
|
||||||
useEffect( () => {
|
useEffect( () => {
|
||||||
if ( isEmpty ) {
|
if ( isEmpty ) {
|
||||||
const initialBlock = createBlock( 'core/paragraph', {
|
const initialBlock = createBlock( 'core/paragraph', {
|
||||||
content: '',
|
content: '',
|
||||||
|
placeholder,
|
||||||
} );
|
} );
|
||||||
insertBlock( initialBlock );
|
insertBlock( initialBlock );
|
||||||
|
onChange( [ initialBlock ] );
|
||||||
}
|
}
|
||||||
}, [] );
|
}, [ isEmpty ] );
|
||||||
|
|
||||||
return (
|
return (
|
||||||
/* Gutenberg handles the keyboard events when focusing the content editable area. */
|
/* Gutenberg handles the keyboard events when focusing the content editable area. */
|
||||||
|
@ -60,8 +73,6 @@ export const EditorWritingFlow: React.VFC = () => {
|
||||||
<BlockTools>
|
<BlockTools>
|
||||||
<WritingFlow>
|
<WritingFlow>
|
||||||
<ObserveTyping>
|
<ObserveTyping>
|
||||||
{ /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */ }
|
|
||||||
{ /* @ts-ignore This action is available in the block editor data store. */ }
|
|
||||||
<BlockList />
|
<BlockList />
|
||||||
</ObserveTyping>
|
</ObserveTyping>
|
||||||
</WritingFlow>
|
</WritingFlow>
|
||||||
|
|
|
@ -1,18 +1,14 @@
|
||||||
/**
|
/**
|
||||||
* External dependencies
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
|
import { BaseControl, Popover, SlotFillProvider } from '@wordpress/components';
|
||||||
import { BlockEditorProvider } from '@wordpress/block-editor';
|
import { BlockEditorProvider } from '@wordpress/block-editor';
|
||||||
import { BlockInstance } from '@wordpress/blocks';
|
import { BlockInstance } from '@wordpress/blocks';
|
||||||
import { SlotFillProvider } from '@wordpress/components';
|
import { createElement, useEffect, useState, useRef } from '@wordpress/element';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import {
|
|
||||||
createElement,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useState,
|
|
||||||
useRef,
|
|
||||||
} from '@wordpress/element';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { uploadMedia } from '@wordpress/media-utils';
|
||||||
|
import { useUser } from '@woocommerce/data';
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore No types for this exist yet.
|
// @ts-ignore No types for this exist yet.
|
||||||
// eslint-disable-next-line @woocommerce/dependency-group
|
// eslint-disable-next-line @woocommerce/dependency-group
|
||||||
|
@ -28,16 +24,20 @@ registerBlocks();
|
||||||
|
|
||||||
type RichTextEditorProps = {
|
type RichTextEditorProps = {
|
||||||
blocks: BlockInstance[];
|
blocks: BlockInstance[];
|
||||||
|
label?: string;
|
||||||
onChange: ( changes: BlockInstance[] ) => void;
|
onChange: ( changes: BlockInstance[] ) => void;
|
||||||
entryId?: string;
|
entryId?: string;
|
||||||
|
placeholder?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RichTextEditor: React.VFC< RichTextEditorProps > = ( {
|
export const RichTextEditor: React.VFC< RichTextEditorProps > = ( {
|
||||||
blocks,
|
blocks,
|
||||||
|
label,
|
||||||
onChange,
|
onChange,
|
||||||
|
placeholder = '',
|
||||||
} ) => {
|
} ) => {
|
||||||
const blocksRef = useRef( blocks );
|
const blocksRef = useRef( blocks );
|
||||||
|
const { currentUserCan } = useUser();
|
||||||
const [ , setRefresh ] = useState( 0 );
|
const [ , setRefresh ] = useState( 0 );
|
||||||
|
|
||||||
// If there is a props change we need to update the ref and force re-render.
|
// If there is a props change we need to update the ref and force re-render.
|
||||||
|
@ -59,8 +59,29 @@ export const RichTextEditor: React.VFC< RichTextEditorProps > = ( {
|
||||||
forceRerender();
|
forceRerender();
|
||||||
}, 200 );
|
}, 200 );
|
||||||
|
|
||||||
|
const mediaUpload = currentUserCan( 'upload_files' )
|
||||||
|
? ( {
|
||||||
|
onError,
|
||||||
|
...rest
|
||||||
|
}: {
|
||||||
|
onError: ( message: string ) => void;
|
||||||
|
} ) => {
|
||||||
|
uploadMedia(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore The upload function passes the remaining required props.
|
||||||
|
{
|
||||||
|
onError: ( { message } ) => onError( message ),
|
||||||
|
...rest,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="woocommerce-rich-text-editor">
|
<div className="woocommerce-rich-text-editor">
|
||||||
|
{ label && (
|
||||||
|
<BaseControl.VisualLabel>{ label }</BaseControl.VisualLabel>
|
||||||
|
) }
|
||||||
<SlotFillProvider>
|
<SlotFillProvider>
|
||||||
<BlockEditorProvider
|
<BlockEditorProvider
|
||||||
value={ blocksRef.current }
|
value={ blocksRef.current }
|
||||||
|
@ -70,13 +91,19 @@ export const RichTextEditor: React.VFC< RichTextEditorProps > = ( {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore This property was recently added in the block editor data store.
|
// @ts-ignore This property was recently added in the block editor data store.
|
||||||
__experimentalClearBlockSelection: false,
|
__experimentalClearBlockSelection: false,
|
||||||
|
mediaUpload,
|
||||||
} }
|
} }
|
||||||
onInput={ debounceChange }
|
onInput={ debounceChange }
|
||||||
onChange={ debounceChange }
|
onChange={ debounceChange }
|
||||||
>
|
>
|
||||||
<ShortcutProvider>
|
<ShortcutProvider>
|
||||||
<EditorWritingFlow />
|
<EditorWritingFlow
|
||||||
|
blocks={ blocksRef.current }
|
||||||
|
onChange={ onChange }
|
||||||
|
placeholder={ placeholder }
|
||||||
|
/>
|
||||||
</ShortcutProvider>
|
</ShortcutProvider>
|
||||||
|
<Popover.Slot />
|
||||||
</BlockEditorProvider>
|
</BlockEditorProvider>
|
||||||
</SlotFillProvider>
|
</SlotFillProvider>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,27 +1,22 @@
|
||||||
|
$toolbar-height: 40px;
|
||||||
|
|
||||||
.woocommerce-rich-text-editor {
|
.woocommerce-rich-text-editor {
|
||||||
border: 1px solid $gray-600;
|
.woocommerce-rich-text-editor__writing-flow {
|
||||||
border-radius: 2px;
|
border: 1px solid $gray-600;
|
||||||
background: $white;
|
border-radius: 2px;
|
||||||
|
background: $white;
|
||||||
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.block-editor-inserter {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.block-editor-block-contextual-toolbar.is-fixed,
|
.block-editor-block-contextual-toolbar.is-fixed,
|
||||||
.block-editor-block-contextual-toolbar.is-fixed .block-editor-block-toolbar .components-toolbar-group,
|
.block-editor-block-contextual-toolbar.is-fixed .block-editor-block-toolbar .components-toolbar-group,
|
||||||
.block-editor-block-contextual-toolbar.is-fixed .block-editor-block-toolbar .components-toolbar {
|
.block-editor-block-contextual-toolbar.is-fixed .block-editor-block-toolbar .components-toolbar {
|
||||||
border-color: $gray-600;
|
border-color: $gray-600;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide rich text placeholder text */
|
|
||||||
.rich-text [data-rich-text-placeholder] {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* hide block boundary background styling */
|
/* hide block boundary background styling */
|
||||||
.rich-text:focus *[data-rich-text-format-boundary] {
|
.rich-text:focus *[data-rich-text-format-boundary] {
|
||||||
background: none !important;
|
background: none !important;
|
||||||
|
@ -37,11 +32,6 @@
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.block-editor-block-list__empty-block-inserter,
|
|
||||||
.block-editor-block-list__insertion-point {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.block-editor-writing-flow {
|
.block-editor-writing-flow {
|
||||||
padding: $gap-small;
|
padding: $gap-small;
|
||||||
}
|
}
|
||||||
|
@ -54,11 +44,25 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.components-accessible-toolbar {
|
.components-accessible-toolbar {
|
||||||
|
height: $toolbar-height;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: $white;
|
background-color: $white;
|
||||||
border-color: $gray-700;
|
border-color: $gray-700;
|
||||||
border-bottom-left-radius: 0;
|
border-bottom-left-radius: 0;
|
||||||
border-bottom-right-radius: 0;
|
border-bottom-right-radius: 0;
|
||||||
|
|
||||||
|
.components-button {
|
||||||
|
height: $toolbar-height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-editor-block-mover:not(.is-horizontal) .block-editor-block-mover__move-button-container > * {
|
||||||
|
height: calc( $toolbar-height / 2 );
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-editor-block-contextual-toolbar.is-fixed,
|
||||||
|
.components-toolbar-group {
|
||||||
|
min-height: $toolbar-height;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wp-block-quote {
|
.wp-block-quote {
|
||||||
|
|
|
@ -14,6 +14,8 @@ export const HEADING_BLOCK_ID = 'core/heading';
|
||||||
export const LIST_BLOCK_ID = 'core/list';
|
export const LIST_BLOCK_ID = 'core/list';
|
||||||
export const LIST_ITEM_BLOCK_ID = 'core/list-item';
|
export const LIST_ITEM_BLOCK_ID = 'core/list-item';
|
||||||
export const QUOTE_BLOCK_ID = 'core/quote';
|
export const QUOTE_BLOCK_ID = 'core/quote';
|
||||||
|
export const IMAGE_BLOCK_ID = 'core/image';
|
||||||
|
export const VIDEO_BLOCK_ID = 'core/video';
|
||||||
|
|
||||||
const ALLOWED_CORE_BLOCKS = [
|
const ALLOWED_CORE_BLOCKS = [
|
||||||
PARAGRAPH_BLOCK_ID,
|
PARAGRAPH_BLOCK_ID,
|
||||||
|
@ -21,6 +23,8 @@ const ALLOWED_CORE_BLOCKS = [
|
||||||
LIST_BLOCK_ID,
|
LIST_BLOCK_ID,
|
||||||
LIST_ITEM_BLOCK_ID,
|
LIST_ITEM_BLOCK_ID,
|
||||||
QUOTE_BLOCK_ID,
|
QUOTE_BLOCK_ID,
|
||||||
|
IMAGE_BLOCK_ID,
|
||||||
|
VIDEO_BLOCK_ID,
|
||||||
];
|
];
|
||||||
|
|
||||||
const registerCoreBlocks = () => {
|
const registerCoreBlocks = () => {
|
||||||
|
|
|
@ -46,11 +46,11 @@
|
||||||
@import 'tag/style.scss';
|
@import 'tag/style.scss';
|
||||||
@import 'text-control/style.scss';
|
@import 'text-control/style.scss';
|
||||||
@import 'text-control-with-affixes/style.scss';
|
@import 'text-control-with-affixes/style.scss';
|
||||||
|
@import 'tooltip/style.scss';
|
||||||
@import 'timeline/style.scss';
|
@import 'timeline/style.scss';
|
||||||
@import 'view-more-list/style.scss';
|
@import 'view-more-list/style.scss';
|
||||||
@import 'web-preview/style.scss';
|
@import 'web-preview/style.scss';
|
||||||
@import 'badge/style.scss';
|
@import 'badge/style.scss';
|
||||||
@import 'dynamic-form/style.scss';
|
@import 'dynamic-form/style.scss';
|
||||||
@import 'enriched-label/style.scss';
|
|
||||||
@import 'tour-kit/style.scss';
|
@import 'tour-kit/style.scss';
|
||||||
@import 'collapsible-content/style.scss';
|
@import 'collapsible-content/style.scss';
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './tooltip';
|
|
@ -0,0 +1,36 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { createElement } from '@wordpress/element';
|
||||||
|
import { Icon, warning } from '@wordpress/icons';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { Tooltip } from '../';
|
||||||
|
|
||||||
|
export const Basic = () => {
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
text={
|
||||||
|
<>
|
||||||
|
This is a <strong>tooltip</strong>!
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CustomIcon = () => {
|
||||||
|
return (
|
||||||
|
<Tooltip text="I'm a tooltip with a custom button icon">
|
||||||
|
<Icon icon={ warning } />
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'WooCommerce Admin/experimental/Tooltip',
|
||||||
|
component: Tooltip,
|
||||||
|
};
|
|
@ -0,0 +1,14 @@
|
||||||
|
.woocommerce-tooltip {
|
||||||
|
display: inline-flex;
|
||||||
|
|
||||||
|
.woocommerce-tooltip__button {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__text .components-popover__content {
|
||||||
|
font-size: $default-font-size;
|
||||||
|
padding: $gap;
|
||||||
|
width: max-content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue