Merged trunk into add/post-external-smoke-test-results-to-pr

This commit is contained in:
jamelreid 2021-12-20 10:51:04 -05:00
commit 5b63fa9a75
100 changed files with 3925 additions and 2673 deletions

View File

@ -1,3 +1,4 @@
module.exports = { module.exports = {
root: true,
extends: [ 'plugin:@woocommerce/eslint-plugin/recommended' ], extends: [ 'plugin:@woocommerce/eslint-plugin/recommended' ],
}; };

View File

@ -1,12 +0,0 @@
---
name: "\U0001F512 Security issue"
about: Please report security issues *only* via https://www.hackerone.com
title: ''
labels: ''
assignees: ''
---
For security reasons, please report all security issues via https://hackerone.com/automattic/. Also, if the issue is valid, a bug bounty will be paid out to you.
Please disclose responsibly and not via GitHub (which allows for exploiting issues in the wild before the patch is released).

95
.github/ISSUE_TEMPLATE/1-bug-report.yml vendored Normal file
View File

@ -0,0 +1,95 @@
name: 🐞 Bug Report
description: Report a bug if something isn't working as expected in WooCommerce Core.
body:
- type: markdown
attributes:
value: |
### Thanks for contributing!
Please provide us with the information requested in this bug report.
Without these details, we won't be able to fully evaluate this issue.
Bug reports lacking detail, or for any other reason than to report a bug, may be closed without action.
While our goal is to address all the issues reported in this repository, GitHub should be treated as a place to report confirmed bugs only.
- If you have a support request or custom code related question please follow one of the steps below:
- Review [WooCommerce Self-Service Guide](https://woocommerce.com/document/woocommerce-self-service-guide/) to see if the solutions listed there apply to your case;
- If you are a paying customer of WooCommerce, contact WooCommerce support by [opening a ticket or starting a live chat](https://woocommerce.com/contact-us/);
- Make a post on [WooCommerce community forum](https://wordpress.org/support/plugin/woocommerce/)
- To get help on custom code questions go to the [WooCommerce Community Slack](https://woocommerce.com/community-slack/) and visit the `#developers` channel.
Make sure to look through the [existing `type: bug` issues](https://github.com/woocommerce/woocommerce/issues?q=is%3Aopen+is%3Aissue+label%3A%22type%3A+bug%22) to see whether your bug has already been submitted.
Feel free to contribute to any existing issues.
Search tip: You can filter our issues using [our labels](https://github.com/woocommerce/woocommerce/labels).
Search tip: Make use of [GitHub's search syntax to refine your search](https://help.github.com/en/github/searching-for-information-on-github/searching-issues-and-pull-requests).
- type: checkboxes
id: prerequisites
attributes:
label: Prerequisites
description: Please confirm these before submitting the issue.
options:
- label: I have carried out troubleshooting steps and I believe I have found a bug.
- label: I have searched for similar bugs in both open and closed issues and cannot find a duplicate.
validations:
required: true
- type: textarea
id: summary
attributes:
label: Describe the bug
description: A clear and concise description of what the bug is.
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected behavior
placeholder: |
A clear and concise description of what you expected to happen.
validations:
required: true
- type: textarea
id: actual
attributes:
label: Actual behavior
placeholder: |
A clear and concise description of what actually happens. Please be as descriptive as possible;
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to reproduce
description: Attach screenshot(s) or recording(s) directly by dragging & dropping.
placeholder: |
1. Go to
2. Click on
3. Scroll down to
4. See error
validations:
required: true
- type: textarea
id: environment
attributes:
label: WordPress Environment
description: |
We use the [WooCommerce System Status Report](https://woocommerce.com/document/understanding-the-woocommerce-system-status-report/) to help us evaluate the issue.
Without this report we won't be able to fully evaluate this issue.
placeholder: |
The System Status Report is found in your WordPress admin under **WooCommerce > Status**.
Please select “Get system report”, then “Copy for support”, and then paste it here.
validations:
required: true
- type: checkboxes
id: isolating
attributes:
label: Isolating the problem
description: |
Please try testing your site for theme and plugins conflict.
To do that deactivate all plugins except for WooCommerce and switch to a default WordPress theme or [Storefront](https://en-gb.wordpress.org/themes/storefront/). Then test again.
If the issue is resolved with the default theme and all plugins deactivated, it means that one of your plugins or a theme is causing the issue.
You will then need to enable it one by one and test every time you do that in order to figure out which plugin is causing the issue.
options:
- label: I have deactivated other plugins and confirmed this bug occurs when only WooCommerce plugin is active.
- label: This bug happens with a default WordPress theme active, or [Storefront](https://woocommerce.com/storefront/).
- label: I can reproduce this bug consistently using the steps above.
validations:
required: true

View File

@ -1,20 +0,0 @@
---
name: "\U0001F47D External issues"
about: Please report WooCommerce Admin, WooCommerce Gutenberg Products Blocks or Action Scheduler issues directly to their respective repositories.
title: ''
labels: ''
assignees: ''
---
Please report issues for the following features directly to their respective repositories.
WooCommerce Admin: https://github.com/woocommerce/woocommerce-admin
WooCommerce Gutenberg Products Blocks: https://github.com/woocommerce/woocommerce-gutenberg-products-block
Action Scheduler: https://github.com/woocommerce/action-scheduler
WooCommerce REST API Docs: https://github.com/woocommerce/woocommerce-rest-api-docs
WooCommerce Code Reference: https://github.com/woocommerce/code-reference

View File

@ -0,0 +1,33 @@
name: ✨ Enhancement Request
description: If you have an idea to improve an existing feature in core or need something for development (such as a new hook) please let us know or submit a Pull Request!
title: "[Enhancement]: "
labels: ["type: enhancement"]
body:
- type: markdown
attributes:
value: |
### Thanks for contributing!
Please provide us with the information requested in this form.
Make sure to look through [existing `type: enhancement` issues](https://github.com/woocommerce/woocommerce/issues?q=is%3Aopen+is%3Aissue+label%3A%22type%3A+enhancement%22) and [existing `votes needed` issues](https://github.com/woocommerce/woocommerce/issues?q=is%3Aissue+sort%3Areactions-%2B1-desc+label%3A%22votes+needed%22) to see whether your idea is already being discussed.
Feel free to contribute to any existing issues.
Search tip: You can filter our issues using [our enhancement label](https://github.com/woocommerce/woocommerce/issues?q=is%3Aissue+label%3A%22type%3A+enhancement%22+).
Search tip: Make use of [GitHub's search syntax to refine your search](https://help.github.com/en/github/searching-for-information-on-github/searching-issues-and-pull-requests).
- type: textarea
id: summary
attributes:
label: Describe the solution you'd like
description: A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
id: alternative
attributes:
label: Describe alternatives you've considered
description: A clear and concise description of any alternative solutions or features you've considered.
- type: textarea
id: context
attributes:
label: Additional context
description: Add any other context or screenshots about the feature request here.

View File

@ -1,28 +0,0 @@
---
name: "❓ Support Question"
about: "If you have a question \U0001F4AC please see our docs or use our forums, helpdesk,
or Slack Community!"
title: ''
labels: ''
assignees: ''
---
We don't offer technical support on GitHub so we recommend using the following:
**Reading our documentation**
Usage docs can be found here: https://docs.woocommerce.com/
If you have a problem, you may want to start with the self help guide here: https://docs.woocommerce.com/document/woocommerce-self-service-guide/
**Technical support for premium extensions or if you're a WooCommerce.com customer**
Contact WooCommerce support by opening a ticket.
https://woocommerce.com/contact-us/
**For help with custom code**
WooCommerce Slack Community: https://woocommerce.com/community-slack/ in the `#developers` channel.
**General usage and development questions**
- WooCommerce Slack Community: https://woocommerce.com/community-slack/
- WordPress.org Forums: https://wordpress.org/support/plugin/woocommerce
- The Official WooCommerce Facebook Group https://www.facebook.com/groups/advanced.woocommerce/

View File

@ -1,58 +0,0 @@
---
name: "\U0001F41E Bug report"
about: Report a bug if something isn't working as expected in the core WooCommerce
plugin.
title: ''
labels: ''
assignees: ''
---
Please provide us with the information requested in this bug report. Without these details, we won't be able to fully evaluate this issue.
Bug reports lacking detail, or for any other reason than to report a bug, may be closed without action.
<!-- This template is for confirmed bugs only. If you have a support request or custom code related question please see our docs or use our forums, helpdesk, or Slack Community! https://github.com/woocommerce/woocommerce/issues/new?assignees=&labels=&template=3-Support.md&title= -->
<!-- Make sure to look through the existing issues to see whether your bug has already been submitted. Feel free to contribute to any existing issues. -->
<!-- Search tip: You can filter our issues using our component labels https://github.com/woocommerce/woocommerce/labels?q=component -->
<!-- Search tip: Make use of GitHub's search syntax to refine your search https://help.github.com/en/github/searching-for-information-on-github/searching-issues-and-pull-requests -->
**Prerequisites (mark completed items with an [x]):**
- [ ] I have carried out troubleshooting steps and I believe I have found a bug.
- [ ] I have searched for similar bugs in both open and closed issues and cannot find a duplicate.
**Describe the bug**
A clear and concise description of what the bug is.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Actual behavior**
A clear and concise description of what actually happens. Please be as descriptive as possible;
**Steps to reproduce the bug (We need to be able to reproduce the bug in order to fix it.)**
Steps to reproduce the bug:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Screenshots**
If applicable, add screenshots to help explain your problem.
<!-- Please try testing your site for theme and plugins conflict. To do that deactivate all plugins except for WooCommerce and switch to a default WordPress theme or [Storefront](https://en-gb.wordpress.org/themes/storefront/). Then test again. If the issue is resolved with the default theme and all plugins deactivated, it means that one of your plugins or a theme is causing the issue. You will then need to enable it one by one and test every time you do that in order to figure out which plugin is causing the issue. -->
**Isolating the problem (mark completed items with an [x]):**
- [ ] I have deactivated other plugins and confirmed this bug occurs when only WooCommerce plugin is active.
- [ ] This bug happens with a default WordPress theme active, or [Storefront](https://woocommerce.com/storefront/).
- [ ] I can reproduce this bug consistently using the steps above.
**WordPress Environment**
We use the [WooCommerce System Status Report](https://docs.woocommerce.com/document/understanding-the-woocommerce-system-status-report/) to help us evaluate the issue.
Without this report we won't be able to fully evaluate this issue.
<details>
```
The System Status Report is found in your WordPress admin under **WooCommerce > Status**.
Please select “Get system report”, then “Copy for support”, and then paste it here.
```
</details>

View File

@ -1,25 +0,0 @@
---
name: "✨ New Enhancement"
about: If you have an idea to improve an existing feature in core or need something
for development (such as a new hook) please let us know or submit a Pull Request!
title: ''
labels: ''
assignees: ''
---
<!-- Make sure to look through existing issues to see whether your idea is already being discussed. Feel free to contribute to any existing issues. -->
<!-- Search tip: You can filter issues using our enhancement label https://github.com/woocommerce/woocommerce/issues?q=is%3Aissue+label%3Aenhancement -->
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@ -1,25 +0,0 @@
---
name: "\U0001F680 Feature request"
about: "Suggest a new feature \U0001F389 We'll consider building it if it receives
sufficient interest! \U0001F44D"
title: ''
labels: ''
assignees: ''
---
<!-- Make sure to look through existing issues to see whether your idea is already being discussed. Feel free to contribute to any existing issues. -->
<!-- Search tip: You can filter issues using our enhancement label https://github.com/woocommerce/woocommerce/issues?q=is%3Aissue+label%3Aenhancement -->
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

14
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,14 @@
blank_issues_enabled: true
contact_links:
- name: 🔒 Security issue
url: https://hackerone.com/automattic/
about: For security reasons, please report all security issues via HackerOne. If the issue is valid, a bug bounty will be paid out to you. Please disclose responsibly and not via GitHub (which allows for exploiting issues in the wild before the patch is released).
- name: ❓ Support Question
url: https://woocommerce.com/document/woocommerce-self-service-guide/
about: If you have a question please see our docs or use our forums, helpdesk, or Slack community!
- name: WooCommerce Admin
url: https://github.com/woocommerce/woocommerce-admin
about: Please report issues for WooCommerce Admin (such as Analytics and Onboarding) directly to it's repository.
- name: WooCommerce Blocks
url: https://github.com/woocommerce/woocommerce-gutenberg-products-block
about: Please report issues for WooCommerce Blocks directly to it's repository.

View File

@ -52,3 +52,103 @@ jobs:
pnpx wc-e2e test:e2e plugins/woocommerce/tests/e2e/specs/smoke-tests/update-woocommerce.js pnpx wc-e2e test:e2e plugins/woocommerce/tests/e2e/specs/smoke-tests/update-woocommerce.js
pnpx wc-e2e test:e2e pnpx wc-e2e test:e2e
pnpx wc-api-tests test api pnpx wc-api-tests test api
build:
name: Build zip for PR
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Build
id: build
uses: woocommerce/action-build@trunk
env:
BUILD_ENV: e2e
- name: Upload PR zip
uses: actions/upload-artifact@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
name: woocommerce
path: ${{ steps.build.outputs.zip_path }}
retention-days: 7
test-plugins:
name: Smoke tests with ${{ matrix.plugin }} plugin installed
runs-on: ubuntu-18.04
needs: [build]
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: 'woocommerce/woocommerce-services'
- plugin: 'WooCommerce Subscriptions'
repo: WC_SUBSCRIPTIONS_REPO
private: true
- plugin: 'WordPress SEO' # Yoast SEO in the UI, but the slug is wordpress-seo
repo: 'Yoast/wordpress-seo'
- plugin: 'Contact Form 7'
repo: 'takayukister/contact-form-7'
steps:
- name: Create dirs.
run: |
mkdir -p code/woocommerce
mkdir -p package/woocommerce
mkdir -p tmp/woocommerce
mkdir -p node_modules
- name: Checkout code.
uses: actions/checkout@v2
with:
path: package/woocommerce
- name: Install PNPM and install dependencies
working-directory: package/woocommerce
run: |
npm install -g pnpm
pnpm install
- name: Load docker images and start containers.
working-directory: package/woocommerce/plugins/woocommerce
run: pnpx wc-e2e docker:up
- name: Move current directory to code. We will install zip file in this dir later.
run: mv ./package/woocommerce/plugins/woocommerce/* ./code/woocommerce
- name: Download WooCommerce ZIP.
uses: actions/download-artifact@v2
with:
name: woocommerce
path: tmp
- name: Extract and replace WooCommerce zip.
working-directory: tmp
run: |
unzip woocommerce.zip -d woocommerce
mv woocommerce/woocommerce/* ../package/woocommerce/plugins/woocommerce/
- name: Install dependencies again
working-directory: package/woocommerce
run: |
npm install -g pnpm
pnpm install
- name: Run tests command.
working-directory: package/woocommerce/plugins/woocommerce
env:
WC_E2E_SCREENSHOTS: 1
E2E_SLACK_TOKEN: ${{ secrets.SMOKE_TEST_SLACK_TOKEN }}
E2E_SLACK_CHANNEL: ${{ secrets.RELEASE_TEST_SLACK_CHANNEL }}
GITHUB_REPOSITORY: ${{ matrix.private && secrets[matrix.repo] || matrix.repo }}
PLUGIN_NAME: ${{ matrix.plugin }}
GITHUB_TOKEN: ${{ secrets.E2E_GH_TOKEN }}
run: |
pnpx wc-e2e test:e2e plugins/woocommerce/tests/e2e/specs/smoke-tests/upload-plugin.js
pnpm nx test-e2e woocommerce

View File

@ -105,3 +105,74 @@ jobs:
E2E_SLACK_TOKEN: ${{ secrets.SMOKE_TEST_SLACK_TOKEN }} E2E_SLACK_TOKEN: ${{ secrets.SMOKE_TEST_SLACK_TOKEN }}
E2E_SLACK_CHANNEL: ${{ secrets.RELEASE_TEST_SLACK_CHANNEL }} E2E_SLACK_CHANNEL: ${{ secrets.RELEASE_TEST_SLACK_CHANNEL }}
run: pnpm nx test-e2e woocommerce run: pnpm nx test-e2e woocommerce
test-plugins:
name: Smoke tests with ${{ matrix.plugin }} plugin installed
runs-on: ubuntu-18.04
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: 'woocommerce/woocommerce-services'
- plugin: 'WooCommerce Subscriptions'
repo: WC_SUBSCRIPTIONS_REPO
private: true
- plugin: 'WordPress SEO' # Yoast SEO in the UI, but the slug is wordpress-seo
repo: 'Yoast/wordpress-seo'
- plugin: 'Contact Form 7'
repo: 'takayukister/contact-form-7'
steps:
- name: Create dirs.
run: |
mkdir -p code/woocommerce
mkdir -p package/woocommerce
mkdir -p tmp/woocommerce
mkdir -p node_modules
- name: Checkout code.
uses: actions/checkout@v2
with:
path: package/woocommerce
- name: Install PNPM and install dependencies
working-directory: package/woocommerce
run: |
npm install -g pnpm
pnpm install
- name: Load docker images and start containers.
working-directory: package/woocommerce/plugins/woocommerce
env:
LATEST_WP_VERSION_MINUS: ${{ matrix.wp }}
run: pnpm nx docker-up woocommerce
- name: Move current directory to code. We will install zip file in this dir later.
run: mv ./package/woocommerce/plugins/woocommerce/* ./code/woocommerce
- name: Download WooCommerce release zip
working-directory: tmp
run: |
ASSET_ID=$(jq ".release.assets[0].id" $GITHUB_EVENT_PATH)
curl https://api.github.com/repos/woocommerce/woocommerce/releases/assets/${ASSET_ID} -LJOH 'Accept: application/octet-stream'
unzip woocommerce.zip -d woocommerce
mv woocommerce/woocommerce/* ../package/woocommerce/plugins/woocommerce/
- name: Run tests command.
working-directory: package/woocommerce/plugins/woocommerce
env:
WC_E2E_SCREENSHOTS: 1
E2E_SLACK_TOKEN: ${{ secrets.SMOKE_TEST_SLACK_TOKEN }}
E2E_SLACK_CHANNEL: ${{ secrets.RELEASE_TEST_SLACK_CHANNEL }}
GITHUB_REPOSITORY: ${{ matrix.private && secrets[matrix.repo] || matrix.repo }}
PLUGIN_NAME: ${{ matrix.plugin }}
GITHUB_TOKEN: ${{ secrets.E2E_GH_TOKEN }}
run: |
pnpx wc-e2e test:e2e plugins/woocommerce/tests/e2e/specs/smoke-tests/upload-plugin.js
pnpm nx test-e2e woocommerce

29
.gitignore vendored
View File

@ -13,6 +13,15 @@ project.properties
*.sublime-workspace *.sublime-workspace
.sublimelinterrc .sublimelinterrc
# Grunt
none
# Sass
.sass-cache/
# Logs
logs/
# Eslint Cache # Eslint Cache
.eslintcache .eslintcache
@ -40,13 +49,27 @@ vendor/
# TypeScript files # TypeScript files
tsconfig.tsbuildinfo tsconfig.tsbuildinfo
# Node Package Dependencies
package-lock.json
# wp-env config # wp-env config
.wp-env.override.json .wp-env.override.json
# Unit tests # Unit tests
/tmp tmp/
packages/js/e2e-environment/config/default.json
packages/js/e2e-environment/docker/wp-cli/initialize.sh # Composer
vendor/
bin/composer/**/vendor/
lib/vendor/
contributors.md
contributors.html
# Yarn
yarn.lock
# Editors
nbproject/private/
# Test Results # Test Results
test-results.json test-results.json

2
.nvmrc
View File

@ -1 +1 @@
v12 v16

View File

@ -1,3 +1,3 @@
{ {
"extends": "stylelint-config-wordpress", "extends": "@wordpress/stylelint-config",
} }

View File

@ -1,5 +1,110 @@
== Changelog == == Changelog ==
= 6.0.0 2021-12-14 =
**WooCommerce**
* Localization - Improve internationalization and add regions for Chile. ([#30875](https://github.com/woocommerce/woocommerce/pull/30875))
* Localization - Add 'GB' back to countries that are recommended to use automated taxes. ([#31100](https://github.com/woocommerce/woocommerce/pull/31100))
* Enhancement - Improve the performance of the filtering by attributes using the new lookup table. ([#31212](https://github.com/woocommerce/woocommerce/pull/31212))
* Enhancement - Stop using options table to store rate limits. ([#30960](https://github.com/woocommerce/woocommerce/pull/30960))
* Enhancement - Support for dynamic price period in in-app marketplace product cards. ([#31026](https://github.com/woocommerce/woocommerce/pull/31026))
* Enhancement - warning to developers to avoid gotcha with shipping rates. ([#30958](https://github.com/woocommerce/woocommerce/pull/30958))
* Enhancement - Add woocommerce_product_options_shipping_product_data hook to product data metabox. ([#30876](https://github.com/woocommerce/woocommerce/pull/30876))
* Enhancement - Ensure empty arrays can be cached. ([#31077](https://github.com/woocommerce/woocommerce/pull/31077))
* Tweak - Remove the need to invoke LookupDataStore->show_feature() to use the product attributes lookup table. ([#31228](https://github.com/woocommerce/woocommerce/pull/31228))
* Tweak - Add new action hook woocommerce_after_variations_table after the product variations table (within the add-to-cart form). ([#29642](https://github.com/woocommerce/woocommerce/pull/29642))
* Tweak - Email password reset link instead of password for new customers. ([#31257](https://github.com/woocommerce/woocommerce/pull/31257))
* Tweak - Disable autocomplete on quantity input field to prevent stale values in Firefox. ([#31196](https://github.com/woocommerce/woocommerce/pull/31196))
* Tweak - Correct the Iraqi Dinar (IQD) symbol. ([#31070](https://github.com/woocommerce/woocommerce/pull/31070))
* Tweak - Add Product Reviews filter for review comment type to the WordPress comment page. ([#31004](https://github.com/woocommerce/woocommerce/pull/31004))
* Tweak - has_block_template method: Add apply_filters to the function which will enable third-party plugins to override the return value. ([#30997](https://github.com/woocommerce/woocommerce/pull/30997))
* Tweak - Clarify tooltip for when the on-hold email is sent. ([#30970](https://github.com/woocommerce/woocommerce/pull/30970))
* Tweak - Perform check for has product archive if current theme is an FSE theme, and not just if it has current_theme_supports( 'woocommerce' ); ([#31094](https://github.com/woocommerce/woocommerce/pull/31094))
* Tweak - Remove the absolute path to the currency-info.php from within locale-info.php. ([#30935](https://github.com/woocommerce/woocommerce/pull/30935))
* Tweak - Update track properties to follow correct format. ([#30899](https://github.com/woocommerce/woocommerce/pull/30899))
* Tweak - Merge Marketplace and My Subscriptions pages back into one Extensions page. ([#31085](https://github.com/woocommerce/woocommerce/pull/31085))
* Fix - Fixes an issue that prevented database update notices from being dismissed. ([#31075](https://github.com/woocommerce/woocommerce/pull/31075))
* Fix - Duplicate coupon usage count when order is created via admin/API and status is changed. ([#31147](https://github.com/woocommerce/woocommerce/pull/31147))
* Fix - Corrects the display of negative refund values within the order editor screen. ([#30957](https://github.com/woocommerce/woocommerce/pull/30957))
* Fix - Fix bug when creating REST API keys with very long descriptions. ([#30901](https://github.com/woocommerce/woocommerce/pull/30901))
* Fix - Fix products API orderby slug and include. ([#30873](https://github.com/woocommerce/woocommerce/pull/30873))
* Dev - Remove defunct AJAX events. ([#30931](https://github.com/woocommerce/woocommerce/pull/30931))
**WooCommerce Admin - 2.9.0 & 2.9.1 & 2.9.2 **
* Dev - Remove task status endpoint ( [#7841](https://github.com/woocommerce/woocommerce-admin/issues/7841) )
* Fix - Fix ordering and styling issue with WooCommerce Payments payment method promotion. ( [#7943](https://github.com/woocommerce/woocommerce-admin/issues/7943) )
* Fix - Fix ExPlat PHP client ( [#7926](https://github.com/woocommerce/woocommerce-admin/issues/7926) )
* Fix - Fix marketing extensions tracks ( [#7908](https://github.com/woocommerce/woocommerce-admin/issues/7908) )
* Fix - Fix shipping task completion status ( [#8031](https://github.com/woocommerce/woocommerce-admin/issues/8031) )
* Update - Increased number of possible items in Recommended Extensions list from 6 to 9 ( [#7887](https://github.com/woocommerce/woocommerce-admin/issues/7887) )
* Update - Reverts addition of Marketplace and My Subscriptions pages to the Marketplace menu. ( [#7902](https://github.com/woocommerce/woocommerce-admin/issues/7902) )
* Update - Add marketing extensions back to onboarding wizard ( [#7831](https://github.com/woocommerce/woocommerce-admin/issues/7831) )
* Update - Add profile notes. ( [#7861](https://github.com/woocommerce/woocommerce-admin/issues/7861) )
* Update - Change CTA text for personalize store task after completion ( [#7852](https://github.com/woocommerce/woocommerce-admin/issues/7852) )
* Update - Refactor data source poller for re-usability. ( [#7671](https://github.com/woocommerce/woocommerce-admin/issues/7671) )
* Update - Update WC Pay card to include in-person information ( [#7830](https://github.com/woocommerce/woocommerce-admin/issues/7830) )
* Update - Updating navigation link colors ( [#7833](https://github.com/woocommerce/woocommerce-admin/issues/7833) )
* Tweak - Use page title Extensions for Marketplace and My Subscriptions pages. ( [#7901](https://github.com/woocommerce/woocommerce-admin/issues/7901) )
* Tweak - Remove the Spinner component to prevent undesired page flickering. ( [#7886](https://github.com/woocommerce/woocommerce-admin/issues/7886) )
* Tweak - Add route and layout for unmatched path ( [#7503](https://github.com/woocommerce/woocommerce-admin/issues/7503) )
* Tweak - Avoid caching extended info ( [#7819](https://github.com/woocommerce/woocommerce-admin/issues/7819) )
* Tweak - Minor design update for Marketing task. ( [#7732](https://github.com/woocommerce/woocommerce-admin/issues/7732) )
* Fix - Do not clear `current` class from the entire page when updating wp-admin's menu. ( [#7773](https://github.com/woocommerce/woocommerce-admin/issues/7773) )
* Fix - Fix calendar not being dismissed when clicking outside. ( [#7714](https://github.com/woocommerce/woocommerce-admin/issues/7714) )
* Fix - fixed warnings when using AdvancedFilters component. ( [#7704](https://github.com/woocommerce/woocommerce-admin/issues/7704) )
* Fix - Fix Tasklist UI illustrations styling ( [#7858](https://github.com/woocommerce/woocommerce-admin/issues/7858) )
* Fix - Revert experiment task titles back to original ( [#7853](https://github.com/woocommerce/woocommerce-admin/issues/7853) )
* Fix - Ensure homescreen defaults to single column layout. ( [#7969](https://github.com/woocommerce/woocommerce-admin/issues/7969) )
* Fix: Fix shipping task not offering step 3. ( [#7985](https://github.com/woocommerce/woocommerce-admin/issues/7985) )
* Add - Add Avalara to tax task ( [#7874](https://github.com/woocommerce/woocommerce-admin/issues/7874) )
* Add - Add 2col expirement. ( [#7872](https://github.com/woocommerce/woocommerce-admin/issues/7872) )
* Add - Added two column experimental task list ( [#7669](https://github.com/woocommerce/woocommerce-admin/issues/7669) )
* Add - Add header cards for all tasks in Tasklist UI experiment ( [#7838](https://github.com/woocommerce/woocommerce-admin/issues/7838) )
* Add - Add onboarding task docs ( [#7762](https://github.com/woocommerce/woocommerce-admin/issues/7762) )
* Dev - Add method to check for install status ( [#7808](https://github.com/woocommerce/woocommerce-admin/issues/7808) )
* Dev - Refactor tax task into separate components
* Dev - Update the task list to use the new task list REST API ( [#7736](https://github.com/woocommerce/woocommerce-admin/issues/7736) )
* Performance - Only load default tasks during REST requests ( [#7904](https://github.com/woocommerce/woocommerce-admin/issues/7904) )
**WooCommerce Blocks - 6.2.0 & 6.3.0 & 6.3.1 & 6.3.2**
* Enhancement - Legacy Template Block: allow users to delete the block. ( [#5176](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5176) )
* Enhancement - Add placeholder text when modifying product search input in the editor. ( [#5122](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5122) )
* Enhancement - FSE: Add basic product archive block template. ( [#5049](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5049) )
* Enhancement - FSE: Add basic taxonomy block templates. ( [#5063](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5063) )
* Enhancement - FSE: Add single product block template. ( [#5054](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5054) )
* Enhancement - FSE: Remove the do_action( woocommerce_sidebar ); action from the LegacyTemplate.php block. ( [#5097](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5097) )
* Enhancement - Fix duplicate queries in product grids. ( [#5002](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5002) )
* Enhancement - FSE: Add abstract block legacy template for core PHP templates. ( [#4991](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/4991) )
* Enhancement - FSE: Add render logic to BlockTemplateController. ( [#4984](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/4984) )
* Enhancement - Improve accessibility by using self-explaining edit button titles. ( [#5113](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5113) )
* Enhancement - Improve readability of terms and condition text by not displaying the text justified. ( [#5120](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5120) )
* Enhancement - Improve rendering performance for Single Product block. ( [#5107](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5107) )
* Enhancement - Improve the product images placeholder display by adding a light gray border to it. ( [#4950](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/4950) )
* Enhancement - Deprecate the __experimental_woocommerce_blocks_checkout_update_order_from_request action in favour of woocommerce_blocks_checkout_update_order_from_request. ( [#5015](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5015) )
* Enhancement - Deprecate the __experimental_woocommerce_blocks_checkout_update_order_meta action in favour of woocommerce_blocks_checkout_update_order_meta. ( [#5017](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5017) )
* Enhancement - Deprecate the __experimental_woocommerce_blocks_checkout_order_processed action in favour of woocommerce_blocks_checkout_order_processed. ( [#5014](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5014) )
* Enhancement - Cart v2: The cart block, like checkout block, now supports inner blocks that allow for greater customizability. ( [#4973](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/4973) )
* Enhancement - BlockTemplateController: Adds the ability to load and manage block template files. ( [#4981](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/4981) )
* Enhancement - Improve accessibility for the editor view of the Product search block. ( [#4905](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/4905) )
* Fix - Removed WooCommerce block templates from appearing in the template dropdown for a page or post. ( [#5167](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5167) )
* Fix - Fix Country is required error on the Cart block when updating shipping address ( [#5129](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5129) )
* Fix - Fix state validation to compare state codes, and only validate if a country is given ( [#5132](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5132) )
* Fix - Make order note block removable ( [#5139](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5139) )
* Fix - Fix label alignment of the product search in the editor. ( [#5072](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5072) )
* Fix - Fix sale badge alignment on smaller screen. ( [#5061](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5061) )
* Fix - FSE: Fix missing is_custom property for WooCommerce block template objects. ( [#5067](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5067) )
* Fix - Replace incorrect with correct text domain. ( [#5020](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5020) )
* Fix - Scripts using wc-settings or script that depend on it would be enqueued in the footer if theyre enqueued in the header. ( [#5059](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5059) )
* Fix - Fix custom classname support for inner checkout blocks. ( [#4978](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/4978) )
* Fix - Fix a bug in free orders and trial subscription products. ( [#4955](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/4955) )
* Fix - Remove duplicate attributes in saved block HTML. ( [#4941](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/4941) )
* Fix - Fix render error of Filter by Attribute block when no attribute is selected. ( [#4847](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/4847) )
* Fix - Store API Ensure returned customer address state is valid. ( [#4844](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/4844) )
* Fix - fatal error in certain WP 5.9 pre-release versions. ( [#5183](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5183) )
= 5.9.0 2021-11-09 = = 5.9.0 2021-11-09 =
**WooCommerce** **WooCommerce**

View File

@ -18,11 +18,12 @@
}, },
"devDependencies": { "devDependencies": {
"@automattic/nx-composer": "^0.1.0", "@automattic/nx-composer": "^0.1.0",
"@nrwl/cli": "latest", "@nrwl/cli": "^13.3.4",
"@nrwl/linter": "^13.1.4", "@nrwl/linter": "^13.3.4",
"@nrwl/tao": "latest", "@nrwl/devkit": "^13.1.4",
"@nrwl/web": "^13.1.4", "@nrwl/tao": "13.3.4",
"@nrwl/workspace": "latest", "@nrwl/web": "^13.3.4",
"@nrwl/workspace": "^13.3.4",
"@types/node": "14.14.33", "@types/node": "14.14.33",
"@woocommerce/eslint-plugin": "^1.3.0", "@woocommerce/eslint-plugin": "^1.3.0",
"@wordpress/prettier-config": "^1.1.1", "@wordpress/prettier-config": "^1.1.1",

View File

@ -3,6 +3,7 @@
## Added ## Added
- Shipping Zones API Tests - Shipping Zones API Tests
- Shipping Methods API Tests - Shipping Methods API Tests
- Complex Order API Tests
# 0.1.0 # 0.1.0

View File

@ -1,6 +1,16 @@
const { order, getOrderExample } = require( './order' ); const { order, getOrderExample } = require( './order' );
const { coupon } = require( './coupon' ); const { coupon } = require( './coupon' );
const { refund } = require( './refund' ); const { refund } = require( './refund' );
const { getTaxRateExamples } = require( './tax-rate' );
const { getVariationExample } = require( './variation' );
const {
simpleProduct,
variableProduct,
variation,
virtualProduct,
groupedProduct,
externalProduct,
} = require( './products-crud' );
const { getShippingZoneExample } = require( './shipping-zone' ); const { getShippingZoneExample } = require( './shipping-zone' );
const { getShippingMethodExample } = require( './shipping-method' ); const { getShippingMethodExample } = require( './shipping-method' );
const shared = require( './shared' ); const shared = require( './shared' );
@ -11,6 +21,14 @@ module.exports = {
coupon, coupon,
shared, shared,
refund, refund,
getTaxRateExamples,
getVariationExample,
simpleProduct,
variableProduct,
variation,
virtualProduct,
groupedProduct,
externalProduct,
getShippingZoneExample, getShippingZoneExample,
getShippingMethodExample, getShippingMethodExample,
}; };

View File

@ -54,8 +54,27 @@ const variableProduct = {
], ],
}; };
/**
* External product example
*/
const externalProduct = {
name: 'An External Product',
regular_price: '1.00',
type: 'external',
};
/**
* Grouped product example
*/
const groupedProduct = {
name: 'A Grouped Product',
type: 'grouped',
};
module.exports = { module.exports = {
simpleProduct, simpleProduct,
virtualProduct, virtualProduct,
variableProduct, variableProduct,
externalProduct,
groupedProduct,
}; };

View File

@ -0,0 +1,33 @@
/**
* A standard tax rate.
*
* For more details on the tax rate properties, see:
*
* https://woocommerce.github.io/woocommerce-rest-api-docs/#tax-rate-properties
*
*/
const standardTaxRate = {
name: 'Standard Rate',
rate: '10.0000',
class: 'standard',
};
const reducedTaxRate = {
name: 'Reduced Rate',
rate: '1.0000',
class: 'reduced-rate',
};
const zeroTaxRate = {
name: 'Zero Rate',
rate: '0.0000',
class: 'zero-rate',
};
const getTaxRateExamples = () => {
return { standardTaxRate, reducedTaxRate, zeroTaxRate };
};
module.exports = {
getTaxRateExamples,
};

View File

@ -0,0 +1,29 @@
/**
* A basic product variation.
*
* For more details on the product variation properties, see:
*
* https://woocommerce.github.io/woocommerce-rest-api-docs/#product-variations
*
*/
const variation = {
regular_price: '1.00',
attributes: [
{
name: 'Size',
option: 'Large',
},
{
name: 'Colour',
option: 'Red',
},
],
};
const getVariationExample = () => {
return variation;
};
module.exports = {
getVariationExample,
};

View File

@ -2,6 +2,8 @@ const { ordersApi } = require( './orders' );
const { couponsApi } = require( './coupons' ); const { couponsApi } = require( './coupons' );
const { productsApi } = require( './products' ); const { productsApi } = require( './products' );
const { refundsApi } = require( './refunds' ); const { refundsApi } = require( './refunds' );
const { taxRatesApi } = require( './tax-rates' );
const { variationsApi } = require( './variations' );
const { shippingZonesApi } = require( './shipping-zones' ); const { shippingZonesApi } = require( './shipping-zones' );
const { shippingMethodsApi } = require( './shipping-methods' ); const { shippingMethodsApi } = require( './shipping-methods' );
@ -10,6 +12,8 @@ module.exports = {
couponsApi, couponsApi,
productsApi, productsApi,
refundsApi, refundsApi,
taxRatesApi,
variationsApi,
shippingZonesApi, shippingZonesApi,
shippingMethodsApi, shippingMethodsApi,
}; };

View File

@ -0,0 +1,73 @@
/**
* Internal dependencies
*/
const {
getRequest,
postRequest,
putRequest,
deleteRequest,
} = require( '../utils/request' );
const { getTaxRateExamples, shared } = require( '../data' );
/**
* WooCommerce Tax Rates endpoints.
*
* https://woocommerce.github.io/woocommerce-rest-api-docs/#tax-rates
*/
const taxRatesApi = {
name: 'Tax Rates',
create: {
name: 'Create a tax rate',
method: 'POST',
path: 'taxes',
responseCode: 201,
payload: getTaxRateExamples(),
taxRate: async ( taxRate ) => postRequest( 'taxes', taxRate ),
},
retrieve: {
name: 'Retrieve a tax rate',
method: 'GET',
path: 'taxes/<id>',
responseCode: 200,
taxRate: async ( taxRateId ) => taxes( `coupons/${ taxRateId }` ),
},
listAll: {
name: 'List all tax rates',
method: 'GET',
path: 'taxes',
responseCode: 200,
taxRates: async ( queryString = {} ) =>
getRequest( 'taxes', queryString ),
},
update: {
name: 'Update a tax rate',
method: 'PUT',
path: 'taxes/<id>',
responseCode: 200,
payload: getTaxRateExamples(),
taxRate: async ( taxRateId, taxRateDetails ) =>
putRequest( `taxes/${ taxRateId }`, taxRateDetails ),
},
delete: {
name: 'Delete a tax rate',
method: 'DELETE',
path: 'taxes/<id>',
responseCode: 200,
payload: {
force: false,
},
taxRate: async ( taxRateId, deletePermanently ) =>
deleteRequest( `taxes/${ taxRateId }`, deletePermanently ),
},
batch: {
name: 'Batch update tax rates',
method: 'POST',
path: 'taxes/batch',
responseCode: 200,
payload: shared.getBatchPayloadExample( getTaxRateExamples() ),
taxRates: async ( batchUpdatePayload ) =>
postRequest( `taxes/batch`, batchUpdatePayload ),
},
};
module.exports = { taxRatesApi };

View File

@ -0,0 +1,84 @@
/**
* Internal dependencies
*/
const {
getRequest,
postRequest,
putRequest,
deleteRequest,
} = require( '../utils/request' );
const { getVariationExample, shared } = require( '../data' );
/**
* WooCommerce Product Variation endpoints.
*
* https://woocommerce.github.io/woocommerce-rest-api-docs/#product-variations
*/
const variationsApi = {
name: 'Product variations',
create: {
name: 'Create a product variation',
method: 'POST',
path: 'products/<product_id>/variations',
responseCode: 201,
payload: getVariationExample(),
variation: async ( productId, variation ) =>
postRequest( `products/${ productId }/variations`, variation ),
},
retrieve: {
name: 'Retrieve a product variation',
method: 'GET',
path: 'products/<product_id>/variations/<id>',
responseCode: 200,
variation: async ( productId, variationId ) =>
`products/${ productId }/variations/${ variationId }`,
},
listAll: {
name: 'List all product variations',
method: 'GET',
path: 'products/<product_id>/variations',
responseCode: 200,
variations: async ( productId, queryString = {} ) =>
getRequest( `products/${ productId }/variations`, queryString ),
},
update: {
name: 'Update a product variation',
method: 'PUT',
path: 'products/<product_id>/variations/<id>',
responseCode: 200,
payload: getVariationExample(),
variation: async ( productId, variationId, variationDetails ) =>
putRequest(
`products/${ productId }/variations/${ variationId }`,
taxRateDetails
),
},
delete: {
name: 'Delete a product variation',
method: 'DELETE',
path: 'products/<product_id>/variations/<id>',
responseCode: 200,
payload: {
force: false,
},
variation: async ( productId, variationId, deletePermanently ) =>
deleteRequest(
`products/${ productId }/variations/${ variationId }`,
deletePermanently
),
},
batch: {
name: 'Batch update product variations',
method: 'POST',
path: 'products/<product_id>/variations/batch',
responseCode: 200,
payload: shared.getBatchPayloadExample( getVariationExample() ),
variations: async ( batchUpdatePayload ) =>
postRequest(
`products/${ productId }/variations/${ variationId }`,
batchUpdatePayload
),
},
};
module.exports = { variationsApi };

View File

@ -4,6 +4,7 @@
"description": "API tests for WooCommerce", "description": "API tests for WooCommerce",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"preinstall": "npx only-allow pnpm",
"test": "jest", "test": "jest",
"test:api": "jest --group=api", "test:api": "jest --group=api",
"test:hello": "jest --group=hello", "test:hello": "jest --group=hello",

View File

@ -3,6 +3,13 @@
"sourceRoot": "packages/js/api-core-tests", "sourceRoot": "packages/js/api-core-tests",
"projectType": "library", "projectType": "library",
"targets": { "targets": {
"changelog": {
"executor": "./tools/executors/changelogger:changelog",
"options": {
"action": "add",
"cwd": "packages/js/api-core-tests"
}
},
"test": { "test": {
"executor": "@nrwl/workspace:run-script", "executor": "@nrwl/workspace:run-script",
"options": { "options": {

View File

@ -0,0 +1,208 @@
const {
taxRatesApi,
productsApi,
ordersApi,
variationsApi,
} = require( '../../endpoints' );
const {
getOrderExample,
getTaxRateExamples,
getVariationExample,
simpleProduct: defaultSimpleProduct,
variableProduct: defaultVariableProduct,
groupedProduct: defaultGroupedProduct,
externalProduct: defaultExternalProduct,
} = require( '../../data' );
/**
* Simple product with Standard tax rate
*/
const simpleProduct = {
...defaultSimpleProduct,
regular_price: '10.00',
tax_class: 'standard',
};
/**
* Variable product with 1 variation with Reduced tax rate
*/
const variableProduct = {
...defaultVariableProduct,
tax_class: 'reduced-rate',
};
const variation = {
...getVariationExample(),
regular_price: '20.00',
tax_class: 'reduced-rate',
};
/**
* External product with Zero rate tax
*/
const externalProduct = {
...defaultExternalProduct,
regular_price: '400.00',
tax_class: 'zero-rate',
};
/**
* Grouped product
*/
const groupedProduct = defaultGroupedProduct;
/**
* Tax rates for each tax class
*/
const { standardTaxRate, reducedTaxRate, zeroTaxRate } = getTaxRateExamples();
/**
* Delete all pre-existing tax rates.
*/
const deletePreExistingTaxRates = async () => {
const { body } = await taxRatesApi.listAll.taxRates( {
_fields: 'id',
} );
if ( Array.isArray( body ) && body.length > 0 ) {
const ids = body.map( ( { id } ) => id );
await taxRatesApi.batch.taxRates( { delete: ids } );
}
};
/**
* Create a tax rate for each tax class, and save their ID's.
*/
const createTaxRates = async () => {
const taxRates = [ standardTaxRate, reducedTaxRate, zeroTaxRate ];
for ( const taxRate of taxRates ) {
const { body } = await taxRatesApi.create.taxRate( taxRate );
taxRate.id = body.id;
}
};
/**
* Create simple, variable, grouped, and external products.
*/
const createProducts = async () => {
// Create a simple product
const { body: createdSimpleProduct } = await productsApi.create.product(
simpleProduct
);
simpleProduct.id = createdSimpleProduct.id;
// Create a variable product with 1 variation
const { body: createdVariableProduct } = await productsApi.create.product(
variableProduct
);
variableProduct.id = createdVariableProduct.id;
await variationsApi.create.variation( variableProduct.id, variation );
// Create a grouped product using the simple product created earlier.
groupedProduct.grouped_products = [ simpleProduct.id ];
const { body: createdGroupedProduct } = await productsApi.create.product(
groupedProduct
);
groupedProduct.id = createdGroupedProduct.id;
// Create an external product
const { body: createdExternalProduct } = await productsApi.create.product(
externalProduct
);
externalProduct.id = createdExternalProduct.id;
};
/**
* The complex order to be created.
*/
const order = {
...getOrderExample(),
shipping_lines: [],
fee_lines: [],
coupon_lines: [],
line_items: [],
};
/**
* Expected totals
*/
const expectedOrderTotal = '442.20';
const expectedTaxTotal = '2.20';
const expectedSimpleProductTaxTotal = '1.00';
const expectedVariableProductTaxTotal = '0.20';
const expectedExternalProductTaxTotal = '0.00';
/**
*
* Test for adding a complex order with different product types and tax classes.
*
* @group api
* @group orders
*
*/
describe( 'Orders API test', () => {
beforeAll( async () => {
await deletePreExistingTaxRates();
await createTaxRates();
await createProducts();
// Add line items to the order
order.line_items = [
{ product_id: simpleProduct.id },
{ product_id: variableProduct.id },
{ product_id: externalProduct.id },
{ product_id: groupedProduct.id },
];
} );
afterAll( async () => {
// Delete order
await ordersApi.delete.order( order.id, true );
// Delete products
await productsApi.batch.products( {
delete: [
simpleProduct.id,
variableProduct.id,
externalProduct.id,
groupedProduct.id,
],
} );
// Delete tax rates
await taxRatesApi.batch.taxRates( {
delete: [ standardTaxRate.id, zeroTaxRate.id, reducedTaxRate.id ],
} );
} );
it( 'can add complex order', async () => {
// Create the complex order and save its ID.
const { status, body } = await ordersApi.create.order( order );
order.id = body.id;
expect( status ).toEqual( ordersApi.create.responseCode );
// Verify order and tax totals
expect( body.total ).toEqual( expectedOrderTotal );
expect( body.total_tax ).toEqual( expectedTaxTotal );
// Verify total tax of each product line item
const expectedTaxTotalsPerLineItem = [
[ simpleProduct, expectedSimpleProductTaxTotal ],
[ variableProduct, expectedVariableProductTaxTotal ],
[ groupedProduct, expectedSimpleProductTaxTotal ],
[ externalProduct, expectedExternalProductTaxTotal ],
];
for ( const [
product,
expectedLineTaxTotal,
] of expectedTaxTotalsPerLineItem ) {
const { total_tax: actualLineTaxTotal } = body.line_items.find(
( { product_id } ) => product_id === product.id
);
expect( actualLineTaxTotal ).toEqual( expectedLineTaxTotal );
}
} );
} );

View File

@ -1,2 +0,0 @@
# Editors
/nbproject/private/

View File

@ -26,6 +26,7 @@
], ],
"sideEffects": false, "sideEffects": false,
"scripts": { "scripts": {
"preinstall": "npx only-allow pnpm",
"clean": "rm -rf ./dist ./tsconfig.tsbuildinfo", "clean": "rm -rf ./dist ./tsconfig.tsbuildinfo",
"compile": "tsc -b", "compile": "tsc -b",
"build": "pnpm run clean && npm run compile", "build": "pnpm run clean && npm run compile",

View File

@ -3,6 +3,13 @@
"sourceRoot": "packages/js/api/src", "sourceRoot": "packages/js/api/src",
"projectType": "library", "projectType": "library",
"targets": { "targets": {
"changelog": {
"executor": "./tools/executors/changelogger:changelog",
"options": {
"action": "add",
"cwd": "packages/js/api"
}
},
"build": { "build": {
"executor": "@nrwl/workspace:run-script", "executor": "@nrwl/workspace:run-script",
"options": { "options": {

View File

@ -4,6 +4,7 @@
- A `specs/data` folder to store page element data. - A `specs/data` folder to store page element data.
- Tests to verify that different top-level menu and their associated sub-menus load successfully. - Tests to verify that different top-level menu and their associated sub-menus load successfully.
- Test scaffolding via `npx wc-e2e install @woocommerce/e2e-core-tests`
## Changed ## Changed

View File

@ -20,6 +20,18 @@ Follow [E2E setup instructions](https://github.com/woocommerce/woocommerce/blob/
### Setting up core tests ### Setting up core tests
#### Version 0.2.0 or newer
Version 0.2.0 added a test installer that will populate the `tests/e2e/specs` folder with test scripts for all the current core test suite. It also creates sample configuration files including all the configuration data needed to run the core tests.
- Install the e2e-environment `npm install @woocommerce/e2e-environment --save-dev`
- Run the installer `npx wc-e2e install @woocommerce/e2e-core-tests`
- Merge the sample configuration files:
- `tests/e2e/docker/woocommerce.e2e-core-tests.sh` => `initialize.sh`
- `tests/e2e/config/default-woocommerce.e2e-core-tests.json` => `default.json`
#### Version 0.1.X or other test runner
- Create the folder `tests/e2e/specs` in your repository if it does not exist. - Create the folder `tests/e2e/specs` in your repository if it does not exist.
- To add a core test to your test suite, create a new `.test.js` file within `tests/e2e/specs` . Example code to run all the shopper tests: - To add a core test to your test suite, create a new `.test.js` file within `tests/e2e/specs` . Example code to run all the shopper tests:
```js ```js
@ -104,7 +116,7 @@ The functions to access the core tests are:
## Contributing a new test ## Contributing a new test
- In your branch create a new `example-test-name.test.js` under the `tests/e2e/core-tests/specs` folder. - In your branch create a new `example-test-name.test.js` under the appropriate folder in the [`specs`](specs) directory.
- Jest does not allow its global functions to be accessed outside the jest environment. To allow the test code to be published in a package import any jest global functions used in your test - Jest does not allow its global functions to be accessed outside the jest environment. To allow the test code to be published in a package import any jest global functions used in your test
```js ```js
const { const {
@ -130,7 +142,7 @@ const runExampleTestName = () => {
module.exports = runExampleTestName; module.exports = runExampleTestName;
``` ```
- Add your test to `tests/e2e/core-tests/specs/index.js` - Add your test to [`specs/index.js`](specs/index.js)
```js ```js
const runExampleTestName = require( './grouping/example-test-name.test' ); const runExampleTestName = require( './grouping/example-test-name.test' );
// ... // ...

View File

@ -0,0 +1,195 @@
{
"url": "http://localhost:8084/",
"users": {
"admin": {
"username": "admin",
"password": "password"
},
"customer": {
"username": "customer",
"password": "password"
}
},
"products": {
"simple": {
"name": "Simple product"
},
"variable": {
"name": "Variable Product with Three Attributes",
"defaultAttributes": [
{
"id": 0,
"name": "Size",
"option": "Medium"
},
{
"id": 0,
"name": "Colour",
"option": "Blue"
}
],
"attributes": [
{
"id": 0,
"name": "Colour",
"isVisibleOnProductPage": true,
"isForVariations": true,
"options": [
"Red",
"Green",
"Blue"
],
"sortOrder": 0
},
{
"id": 0,
"name": "Size",
"isVisibleOnProductPage": true,
"isForVariations": true,
"options": [
"Small",
"Medium",
"Large"
],
"sortOrder": 0
},
{
"id": 0,
"name": "Logo",
"isVisibleOnProductPage": true,
"isForVariations": true,
"options": [
"Woo",
"WordPress"
],
"sortOrder": 0
}
]
},
"variations": [
{
"regularPrice": "19.99",
"attributes": [
{
"name": "Size",
"option": "Large"
},
{
"name": "Colour",
"option": "Red"
}
]
},
{
"regularPrice": "18.99",
"attributes": [
{
"name": "Size",
"option": "Medium"
},
{
"name": "Colour",
"option": "Green"
}
]
},
{
"regularPrice": "17.99",
"attributes": [
{
"name": "Size",
"option": "Small"
},
{
"name": "Colour",
"option": "Blue"
}
]
}
],
"grouped": {
"name": "Grouped Product with Three Children",
"groupedProducts": [
{
"name": "Base Unit",
"regularPrice": "29.99"
},
{
"name": "Add-on A",
"regularPrice": "11.95"
},
{
"name": "Add-on B",
"regularPrice": "18.97"
}
]
},
"external": {
"name": "External product",
"regularPrice": "24.99",
"buttonText": "Buy now",
"externalUrl": "https://wordpress.org/plugins/woocommerce"
}
},
"coupons": {
"percentage": {
"code": "20percent",
"discountType": "percent",
"amount": "20.00"
}
},
"addresses": {
"admin": {
"store": {
"email": "admin@woocommercecoree2etestsuite.com",
"firstname": "John",
"lastname": "Doe",
"company": "Automattic",
"country": "United States (US)",
"addressfirstline": "addr 1",
"addresssecondline": "addr 2",
"countryandstate": "United States (US) — California",
"city": "San Francisco",
"state": "CA",
"postcode": "94107"
}
},
"customer": {
"billing": {
"firstname": "John",
"lastname": "Doe",
"company": "Automattic",
"country": "United States (US)",
"addressfirstline": "addr 1",
"addresssecondline": "addr 2",
"city": "San Francisco",
"state": "CA",
"postcode": "94107",
"phone": "123456789",
"email": "john.doe@example.com"
},
"shipping": {
"firstname": "John",
"lastname": "Doe",
"company": "Automattic",
"country": "United States (US)",
"addressfirstline": "addr 1",
"addresssecondline": "addr 2",
"city": "San Francisco",
"state": "CA",
"postcode": "94107"
}
}
},
"orders": {
"basicPaidOrder": {
"paymentMethod": "cod",
"status": "processing",
"billing": {
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@example.com"
}
}
}
}

View File

@ -0,0 +1,5 @@
module.exports = {
testSpecs: 'installFiles/scaffold-tests.json',
defaultJson: 'installFiles/default-test-config.json',
initializeSh: 'installFiles/initialize.sh.default',
};

View File

@ -0,0 +1,25 @@
#!/bin/bash
echo "Initializing WooCommerce E2E"
# This is a workaround to accommodate different directory names.
wp plugin activate --all
wp plugin deactivate akismet
wp plugin deactivate hello
wp theme install twentynineteen --activate
wp user create customer customer@woocommercecoree2etestsuite.com \
--user_pass=password \
--role=subscriber \
--first_name='Jane' \
--last_name='Smith' \
--path=/var/www/html
# we cannot create API keys for the API, so we using basic auth, this plugin allows that.
wp plugin install https://github.com/WP-API/Basic-Auth/archive/master.zip --activate
# install the WP Mail Logging plugin to test emails
wp plugin install wp-mail-logging --activate
# initialize pretty permalinks
wp rewrite structure /%postname%/

View File

@ -0,0 +1,138 @@
{
"active": [
{
"name": "front-end",
"description": "Shopper tests",
"testFiles": [
{
"name": "cart-begin",
"functions": [ "runCartPageTest" ]
}, {
"name": "cart-calculate-shipping",
"functions": [ "runCartCalculateShippingTest" ]
}, {
"name": "cart-coupons",
"functions": [ "runCartApplyCouponsTest" ]
}, {
"name": "checkout-begin",
"functions": [ "runCheckoutPageTest" ]
}, {
"name": "checkout-coupons",
"functions": [ "runCheckoutApplyCouponsTest" ]
}, {
"name": "checkout-create-account",
"functions": [ "runCheckoutCreateAccountTest" ]
}, {
"name": "checkout-login-account",
"functions": [ "runCheckoutLoginAccountTest" ]
}, {
"name": "my-account-create-account",
"functions": [ "runMyAccountCreateAccountTest" ]
}, {
"name": "my-account-pay-order",
"functions": [ "runMyAccountPayOrderTest" ]
}, {
"name": "my-account",
"functions": [ "runMyAccountPageTest" ]
}, {
"name": "order-email-receiving",
"functions": [ "runOrderEmailReceivingTest" ]
}, {
"name": "product-browse-search-sort",
"functions": [ "runProductBrowseSearchSortTest" ]
}, {
"name": "single-product-page",
"functions": [ "runSingleProductPageTest" ]
}, {
"name": "variable-product-updates",
"functions": [ "runVariableProductUpdateTest" ]
}
]
}, {
"name": "rest-api",
"description": "REST API tests",
"testFiles": [
{
"name": "api",
"functions": [ "runApiTests" ]
}
]
}, {
"name": "wp-admin",
"description": "Merchant tests",
"testFiles": [
{
"name": "create-coupon",
"functions": [ "runCreateCouponTest" ]
}, {
"name": "create-order",
"functions": [ "runCreateOrderTest" ]
}, {
"name": "create-shipping-classes",
"functions": [ "runAddShippingClassesTest" ]
}, {
"name": "create-shipping-zones",
"functions": [ "runAddNewShippingZoneTest" ]
}, {
"name": "create-simple-product",
"functions": [ "runAddSimpleProductTest" ]
}, {
"name": "create-variable-product",
"functions": [ "runAddVariableProductTest" ]
}, {
"name": "order-coupon",
"functions": [ "runOrderApplyCouponTest" ]
}, {
"name": "order-customer-payment-page",
"functions": [ "runMerchantOrdersCustomerPaymentPage" ]
}, {
"name": "order-edit",
"functions": [ "runEditOrderTest" ]
}, {
"name": "order-emails",
"functions": [ "runMerchantOrderEmailsTest" ]
}, {
"name": "order-refund",
"functions": [ "runOrderRefundTest" ]
}, {
"name": "order-searching",
"functions": [ "runOrderSearchingTest" ]
}, {
"name": "order-status-filters",
"functions": [ "runOrderStatusFiltersTest" ]
}, {
"name": "product-edit",
"functions": [ "runProductEditDetailsTest" ]
}, {
"name": "product-import-csv",
"functions": [ "runImportProductsTest" ]
}, {
"name": "product-search",
"functions": [ "runProductSearchTest" ]
}, {
"name": "update-general-settings",
"functions": [ "runUpdateGeneralSettingsTest" ]
}, {
"name": "update-product-settings",
"functions": [ "runProductSettingsTest" ]
}, {
"name": "update-tax-settings",
"functions": [ "runTaxSettingsTest" ]
}, {
"name": "wccom-connect",
"functions": [ "runInitiateWccomConnectionTest" ]
}
]
}
],
"deprecated": [
{
"name": "example-folder",
"testFiles": [
{ "name": "any-filename-to-deprecate" }
]
}
]
}

View File

@ -23,6 +23,7 @@
"access": "public" "access": "public"
}, },
"scripts": { "scripts": {
"preinstall": "npx only-allow pnpm",
"build": "./bin/build.sh", "build": "./bin/build.sh",
"prepare": "pnpm run build" "prepare": "pnpm run build"
} }

View File

@ -1,5 +1,14 @@
{ {
"root": "packages/js/e2e-core-tests/", "root": "packages/js/e2e-core-tests/",
"sourceRoot": "packages/js/e2e-core-tests", "sourceRoot": "packages/js/e2e-core-tests",
"projectType": "library" "projectType": "library",
"targets": {
"changelog": {
"executor": "./tools/executors/changelogger:changelog",
"options": {
"action": "add",
"cwd": "packages/js/e2e-core-tests"
}
}
}
} }

View File

@ -0,0 +1,2 @@
config/default.json
docker/wp-cli/initialize.sh

View File

@ -8,10 +8,14 @@
## Added ## Added
- Added `await` for every call to `shopper.logout` - Added `await` for every call to `shopper.logout`
- Updated `getLatestReleaseZipUrl()` to allow passing in an authorization token and simplified arguments to just the repository name
- Added `upload.ini` which increases the limits for uploading files (such as for plugins) in the Docker environment
- Test setup, scaffolding, and removal via `wc-e2e install` and `wc-e2e uninstall`
## Fixed ## Fixed
- Updated the browserViewport in `jest.setup.js` to match the `defaultViewport` dimensions defined in `jest-puppeteer.config.js` - Updated the browserViewport in `jest.setup.js` to match the `defaultViewport` dimensions defined in `jest-puppeteer.config.js`
## Added ## Added
- Added quotes around `WORDPRESS_TITLE` value in .env file to address issue with docker compose 2 "key cannot contain a space" error. - Added quotes around `WORDPRESS_TITLE` value in .env file to address issue with docker compose 2 "key cannot contain a space" error.

View File

@ -9,6 +9,19 @@ npm install @woocommerce/e2e-environment --save
npm install jest --global npm install jest --global
``` ```
### Version 0.3.0 and newer
Version 0.3.0 added a test installer that will populate the `tests/e2e/*` folder with test scripts and configuration files. The installer will create test scripts for E2E test packages that include support for the installer.
- [Adding test scaffolding to E2E test packages](https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/e2e-environment/test-packages.md)
#### Using the installer
- Install a default test environment: `npx wc-e2e install`
- Install test specs from an E2E tests package: `npx wc-e2e install @woocommerce-e2e-tests [--format cjs] [--ext spec.js]`
- The default test spec format and extension are `ES6` and `test.js`
- Remove test specs for an E2E tests package: `npx wc-e2e uninstall @woocommerce-e2e-tests`
## Configuration ## Configuration
The `@woocommerce/e2e-environment` package exports configuration objects that can be consumed in JavaScript config files in your project. Additionally, it includes a basic hosting container for running tests and includes instructions for creating your Travis CI setup. The `@woocommerce/e2e-environment` package exports configuration objects that can be consumed in JavaScript config files in your project. Additionally, it includes a basic hosting container for running tests and includes instructions for creating your Travis CI setup.
@ -60,10 +73,10 @@ The E2E environment uses Jest as a test runner. Extending the base config is nec
```js ```js
const path = require( 'path' ); const path = require( 'path' );
const { useE2EJestConfig } = require( '@woocommerce/e2e-environment' ); const { useE2EJestConfig, resolveLocalE2ePath } = require( '@woocommerce/e2e-environment' );
const jestConfig = useE2EJestConfig( { const jestConfig = useE2EJestConfig( {
roots: [ path.resolve( __dirname, '../specs' ) ], roots: [ resolveLocalE2ePath( 'specs' ) ],
} ); } );
module.exports = jestConfig; module.exports = jestConfig;
@ -71,6 +84,20 @@ module.exports = jestConfig;
**NOTE:** Your project's Jest config file is: `tests/e2e/config/jest.config.js`. **NOTE:** Your project's Jest config file is: `tests/e2e/config/jest.config.js`.
### The Jest Object
The E2E environment has the following methods to let us control Jest's overall behavior.
| Function | Parameters | Description |
|-----------|-------------|--------------|
| `setupJestRetries` | `retries` | Sets the amount of retries on failed tests
**NOTE:** The amount of times failed tests are retried can also be set using the `E2E_RETRY_TIMES` environment variable when executing tests. This can be done using the command below:
```
E2E_RETRY_TIMES=2 pnpx wc-e2e test:e2e
```
#### Test Screenshots #### Test Screenshots
The test sequencer provides a screenshot function for test failures. To enable screenshots on test failure use The test sequencer provides a screenshot function for test failures. To enable screenshots on test failure use
@ -142,7 +169,7 @@ The test sequencer uses the following default Puppeteer configuration:
}; };
``` ```
You can customize the configuration in `tests/e2e/config/jest-puppeteer.config.js` You can customize the configuration in [`config/jest-puppeteer.config.js`](config/jest-puppeteer.config.js)
```js ```js
const { useE2EJestPuppeteerConfig } = require( '@woocommerce/e2e-environment' ); const { useE2EJestPuppeteerConfig } = require( '@woocommerce/e2e-environment' );
@ -158,7 +185,7 @@ module.exports = puppeteerConfig;
### Jest Setup ### Jest Setup
Jest provides [setup and teardown functions](https://jestjs.io/docs/setup-teardown) similar to PHPUnit. The default setup and teardown is in [`tests/e2e/env/src/setup/jest.setup.js`](src/setup/jest.setup.js). Additional setup and teardown functions can be added to [`tests/e2e/config/jest.setup.js`](../config/jest.setup.js) Jest provides [setup and teardown functions](https://jestjs.io/docs/setup-teardown) similar to PHPUnit. The default setup and teardown is in [`src/setup/jest.setup.js`](src/setup/jest.setup.js). Additional setup and teardown functions can be added to [`tests/e2e/config/jest.setup.js`](../../../plugins/woocommerce/tests/e2e/config/jest.setup.js)
#### Console filtering #### Console filtering
@ -227,9 +254,9 @@ The above method also makes use of the following utility methods which can also
If you would like to get the latest release zip URL, which can be used in the methods mentioned above, you can use the following helper function to do so: If you would like to get the latest release zip URL, which can be used in the methods mentioned above, you can use the following helper function to do so:
`getLatestReleaseZipUrl( owner, repository, getPrerelease, perPage )` `getLatestReleaseZipUrl( repository, authorizationToken, getPrerelease, perPage )`
This will return a string with the latest release URL. Optionally, you can use the `getPrerelease` boolean flag, which defaults to false, on whether or not to get a prerelease instead. The `perPage` flag can be used to return more results when getting the list of releases. The default value is 3. This will return a string with the latest release URL. Optionally, you can use the `getPrerelease` boolean flag, which defaults to false, on whether or not to get a prerelease instead. The `perPage` flag can be used to return more results when getting the list of releases. The default value is 3. If the repository requires authorization to access, the authorization token can be passed in to the `authorizationToken` argument.
## Additional information ## Additional information

View File

@ -11,6 +11,7 @@ const {
getAppName, getAppName,
getTestConfig, getTestConfig,
resolveLocalE2ePath, resolveLocalE2ePath,
resolvePackagePath,
} = require( '../utils' ); } = require( '../utils' );
const dockerArgs = []; const dockerArgs = [];
@ -63,7 +64,7 @@ if ( appPath ) {
if ( fs.existsSync( appInitFile ) ) { if ( fs.existsSync( appInitFile ) ) {
fs.copyFileSync( fs.copyFileSync(
appInitFile, appInitFile,
path.resolve( __dirname, '../docker/wp-cli/initialize.sh' ) resolvePackagePath( 'docker/wp-cli/initialize.sh' )
); );
console.log( 'Initializing ' + appInitFile ); console.log( 'Initializing ' + appInitFile );
} }
@ -90,7 +91,7 @@ if ( ! process.env.WORDPRESS_URL ) {
} }
// Ensure that the first Docker compose file loaded is from our local env. // Ensure that the first Docker compose file loaded is from our local env.
dockerArgs.unshift( '-f', path.resolve( __dirname, '../docker-compose.yaml' ) ); dockerArgs.unshift( '-f', resolvePackagePath( 'docker-compose.yaml' ) );
const dockerProcess = spawnSync( 'docker-compose', dockerArgs, { const dockerProcess = spawnSync( 'docker-compose', dockerArgs, {
stdio: 'inherit', stdio: 'inherit',

View File

@ -4,7 +4,11 @@ const { spawnSync } = require( 'child_process' );
const program = require( 'commander' ); const program = require( 'commander' );
const path = require( 'path' ); const path = require( 'path' );
const fs = require( 'fs' ); const fs = require( 'fs' );
const { getAppRoot, resolveLocalE2ePath } = require( '../utils' ); const {
getAppRoot,
resolveLocalE2ePath,
resolvePackagePath,
} = require( '../utils' );
const { const {
WC_E2E_SCREENSHOTS, WC_E2E_SCREENSHOTS,
JEST_PUPPETEER_CONFIG, JEST_PUPPETEER_CONFIG,
@ -30,7 +34,7 @@ if ( WC_E2E_SCREENSHOTS ) {
} }
} }
const nodeConfigDirs = [ path.resolve( __dirname, '../config' ) ]; const nodeConfigDirs = [ resolvePackagePath( 'config' ) ];
if ( appPath ) { if ( appPath ) {
nodeConfigDirs.unshift( resolveLocalE2ePath( 'config' ) ); nodeConfigDirs.unshift( resolveLocalE2ePath( 'config' ) );
@ -51,10 +55,7 @@ if ( ! JEST_PUPPETEER_CONFIG ) {
// Use local Puppeteer config if there is one. // Use local Puppeteer config if there is one.
// Load test configuration file into an object. // Load test configuration file into an object.
const localJestConfigFile = resolveLocalE2ePath( 'config/jest-puppeteer.config.js' ); const localJestConfigFile = resolveLocalE2ePath( 'config/jest-puppeteer.config.js' );
const jestConfigFile = path.resolve( const jestConfigFile = resolvePackagePath( 'config/jest-puppeteer.config.js' );
__dirname,
'../config/jest-puppeteer.config.js'
);
testEnvVars.JEST_PUPPETEER_CONFIG = fs.existsSync( localJestConfigFile ) testEnvVars.JEST_PUPPETEER_CONFIG = fs.existsSync( localJestConfigFile )
? localJestConfigFile ? localJestConfigFile
@ -90,7 +91,7 @@ if ( program.debug ) {
const envVars = Object.assign( {}, process.env, testEnvVars ); const envVars = Object.assign( {}, process.env, testEnvVars );
let configPath = path.resolve( __dirname, '../config/jest.config.js' ); let configPath = resolvePackagePath( 'config/jest.config.js' );
// Look for a Jest config in the dependent app's path. // Look for a Jest config in the dependent app's path.
if ( appPath ) { if ( appPath ) {

View File

@ -0,0 +1,233 @@
#!/usr/bin/env node
/**
* External dependencies.
*/
const fs = require( 'fs' );
const path = require( 'path' );
const sprintf = require( 'sprintf-js' ).sprintf;
/**
* Internal dependencies.
*/
const {
resolvePackage,
resolvePackagePath,
} = require( '../utils' );
const {
createLocalE2ePath,
confirm,
confirmLocalCopy,
confirmLocalDelete,
getPackageData,
installDefaults
} = require( '../utils/scaffold' );
const args = process.argv.slice( 2 );
const [ command, packageName ] = args;
// Allow multiple spec file extensions and formats.
let testExtension = 'test.js';
let testFormat = '';
for ( let a = 2; a < args.length; a++ ) {
const nextArg = a + 1;
if ( nextArg >= args.length ) {
break;
}
switch ( args[ a ] ) {
case '--format':
testFormat = args[ nextArg ];
break;
case '--ext':
testExtension = args[ nextArg ];
break;
}
}
/**
* Install the test scripts and sample default.json configuration
*/
if ( command == 'install' ) {
// Install some environment defaults if no package is requested.
if ( ! packageName ) {
installDefaults();
return;
}
// `package` is a reserved word
const pkg = resolvePackage( packageName ).name;
if ( ! pkg.length ) {
//@todo add error message
return;
}
const { packageSlug, testSpecs, defaultJson, initializeSh } = getPackageData( pkg );
// Write sample default.json
if ( defaultJson ) {
const defaultJsonName = `config${path.sep}default-${packageSlug}.json`;
createLocalE2ePath( 'config' );
if ( confirmLocalCopy( defaultJsonName, defaultJson, pkg ) ) {
console.log( `Created sample test configuration to 'tests/e2e/${defaultJsonName}'.` );
}
}
// Write sample initialize.sh
if ( initializeSh ) {
const defaultInitName = `docker${path.sep}${packageSlug}.sh`;
createLocalE2ePath( 'docker' );
if ( confirmLocalCopy( defaultInitName, initializeSh, pkg ) ) {
console.log( `Created sample test container initialization script to 'tests/e2e/${defaultInitName}'.` );
}
}
if ( ! testSpecs ) {
return;
}
// Write test files
const testsSpecFile = resolvePackagePath( testSpecs, pkg );
const specs = fs.readFileSync( testsSpecFile );
const tests = JSON.parse( specs );
const { active, deprecated } = tests;
if ( active && active.length ) {
const blankLine = '';
const eol = "\n";
const autoGenerate = sprintf( '/* This file was auto-generated by the command `npx wc-e2e install %s`. */', packageName );
let importLineFormat;
let overwriteFiles;
let confirmPrompt;
if ( testFormat.toLowerCase() == 'cjs' ) {
importLineFormat = sprintf( "const {%%s} = require( '%s' );", pkg );
} else {
importLineFormat = sprintf( "import {%%s} from '%s';", pkg );
}
// Create the specs folder if not present
let specFolderPath = createLocalE2ePath( 'specs' );
// Loop through folders and files to write test scripts.
for ( let f = 0; f < active.length; f++ ) {
if ( overwriteFiles == 'q' ) {
overwriteFiles = '';
break;
}
const testFolder = active[ f ];
const { testFiles } = testFolder;
if ( ! testFiles || ! testFiles.length ) {
continue;
}
let specFolder;
if ( testFolder.name.length ) {
specFolder = createLocalE2ePath( `specs${path.sep}${testFolder.name}` );
} else {
specFolder = specFolderPath;
}
// Create the test files.
for ( let t = 0; t < testFiles.length; t++ ) {
const testFile = testFiles[ t ];
if ( ! testFile.functions.length ) {
continue;
}
const testFileName = `${testFolder.name}${path.sep}${testFile.name}.${testExtension}`;
const testFilePath = `${specFolder}${path.sep}${testFile.name}.${testExtension}`;
// Check to see if file exists.
if ( fs.existsSync( testFilePath ) ) {
if ( overwriteFiles != 'a' ) {
confirmPrompt = `${testFileName} already exists. Overwrite? [y]es/[n]o/[a]ll/[q]uit: `;
overwriteFiles = confirm( confirmPrompt, 'anqy' );
overwriteFiles = overwriteFiles.toLowerCase();
}
if ( overwriteFiles == 'q' ) {
break;
}
if ( overwriteFiles != 'a' && overwriteFiles != 'y' ) {
continue;
}
}
console.log( 'Writing tests/e2e/specs/' + testFileName );
let buffer = [ autoGenerate ];
let testSeparator, testTerminator, importPrefix;
// Add the import line.
if ( testFile.functions.length > 3 ) {
testSeparator = ',' + eol;
testTerminator = eol;
importPrefix = eol;
} else {
testSeparator = ', ';
testTerminator = ' ';
importPrefix = ' ';
}
const testImport = testFile.functions.join( testSeparator ) + testTerminator;
buffer.push( sprintf( importLineFormat, importPrefix + testImport ), blankLine );
// Add test function calls and write the file
let functionCalls = testFile.functions.map( functionName => functionName + '();' );
buffer.push( ...functionCalls, blankLine );
fs.writeFileSync( testFilePath, buffer.join( eol ) );
}
}
}
// @todo: deprecated files.
} else if ( command == 'uninstall' ) {
if ( ! packageName ) {
// @todo: write error message
return;
}
const pkg = resolvePackage( packageName ).name;
const { packageSlug, testSpecs, defaultJson, initializeSh } = getPackageData( pkg );
// Delete sample default.json
if ( defaultJson ) {
const defaultJsonName = `config${path.sep}default-${packageSlug}.json`;
confirmLocalDelete( defaultJsonName );
}
// Delete sample initialize.sh
if ( initializeSh ) {
const defaultInitName = `docker${path.sep}${packageSlug}.sh`;
confirmLocalDelete( defaultInitName );
}
if ( ! testSpecs ) {
return;
}
const testsSpecFile = resolvePackagePath( testSpecs, pkg );
const specs = fs.readFileSync( testsSpecFile );
const tests = JSON.parse( specs );
const { active } = tests;
if ( ! active || ! active.length ) {
return;
}
// Loop through folders and files to delete test scripts.
for ( let f = 0; f < active.length; f++ ) {
const testFolder = active[ f ];
const { testFiles } = testFolder;
if ( ! testFiles || ! testFiles.length ) {
continue;
}
const specFolder = testFolder.name.length ? `specs${path.sep}${testFolder.name}` : 'specs';
for ( let t = 0; t < testFiles.length; t++ ) {
const testFile = testFiles[ t ];
const testFilePath = `${specFolder}${path.sep}${testFile.name}.${testExtension}`;
confirmLocalDelete( testFilePath );
}
}
}

View File

@ -71,6 +71,10 @@ case $1 in
./bin/wait-for-build.sh && ./bin/e2e-test-integration.js --dev --debug $2 ./bin/wait-for-build.sh && ./bin/e2e-test-integration.js --dev --debug $2
TESTRESULT=$? TESTRESULT=$?
;; ;;
'install' | \
'uninstall')
./bin/scaffold.js $@
;;
*) *)
usage usage
;; ;;

View File

@ -3,15 +3,17 @@
*/ */
const jestConfig = require( './jest.config' ); const jestConfig = require( './jest.config' );
const jestPuppeteerConfig = require( './jest-puppeteer.config' ); const jestPuppeteerConfig = require( './jest-puppeteer.config' );
const jestobjectConfig = require('./jest-object.config');
const { const {
useE2EBabelConfig, useE2EBabelConfig,
useE2EEsLintConfig, useE2EEsLintConfig,
useE2EJestConfig, useE2EJestConfig,
useE2EJestPuppeteerConfig useE2EJestPuppeteerConfig,
} = require( './use-config' ); } = require( './use-config' );
module.exports = { module.exports = {
jestConfig, jestConfig,
...jestobjectConfig,
jestPuppeteerConfig, jestPuppeteerConfig,
useE2EBabelConfig, useE2EBabelConfig,
useE2EEsLintConfig, useE2EEsLintConfig,

View File

@ -0,0 +1,22 @@
/**
* External Dependencies
*/
const { E2E_RETRY_TIMES } = process.env;
const setupJestRetries = ( retries = 0 ) => {
const retryTimes = E2E_RETRY_TIMES ? E2E_RETRY_TIMES : retries;
if ( retryTimes > 0 ) {
jest.retryTimes( retryTimes );
}
};
// If more methods are added to setupJestObject, it should be include in the readme
const setupJestObject = () => {
setupJestRetries();
};
module.exports = {
setupJestObject,
setupJestRetries,
};

View File

@ -55,6 +55,9 @@ const combinedConfig = {
transformIgnorePatterns: [ transformIgnorePatterns: [
'node_modules/(?!(woocommerce)/)', 'node_modules/(?!(woocommerce)/)',
], ],
testRunner: 'jest-circus/runner',
roots: [ testSpecs ], roots: [ testSpecs ],
}; };

View File

@ -35,6 +35,7 @@ services:
WORDPRESS_DEBUG: 1 WORDPRESS_DEBUG: 1
volumes: volumes:
- wordpress:/var/www/html - wordpress:/var/www/html
- ./upload.ini:/usr/local/etc/php/conf.d/uploads.ini
- "../../../${WC_E2E_FOLDER}:${WC_E2E_FOLDER_MAPPING}" - "../../../${WC_E2E_FOLDER}:${WC_E2E_FOLDER_MAPPING}"
wordpress-cli: wordpress-cli:

View File

@ -0,0 +1,25 @@
#!/bin/bash
echo "Initializing WooCommerce E2E"
# This is a workaround to accommodate different directory names.
wp plugin activate --all
wp plugin deactivate akismet
wp plugin deactivate hello
wp theme install twentynineteen --activate
wp user create customer customer@woocommercecoree2etestsuite.com \
--user_pass=password \
--role=subscriber \
--first_name='Jane' \
--last_name='Smith' \
--path=/var/www/html
# we cannot create API keys for the API, so we using basic auth, this plugin allows that.
wp plugin install https://github.com/WP-API/Basic-Auth/archive/master.zip --activate
# install the WP Mail Logging plugin to test emails
wp plugin install wp-mail-logging --activate
# initialize pretty permalinks
wp rewrite structure /%postname%/

View File

@ -0,0 +1,8 @@
const path = require( 'path' );
const { useE2EJestConfig, getAppRoot } = require( '@woocommerce/e2e-environment' );
const jestConfig = useE2EJestConfig( {
roots: [ path.resolve( __dirname, '../specs' ) ],
} );
module.exports = jestConfig;

View File

@ -0,0 +1,80 @@
import {
clearLocalStorage,
setBrowserViewport,
withRestApi,
WP_ADMIN_LOGIN
} from '@woocommerce/e2e-utils';
const config = require( 'config' );
const { HTTPClientFactory } = require( '@woocommerce/api' );
const { addConsoleSuppression, updateReadyPageStatus } = require( '@woocommerce/e2e-environment' );
const { DEFAULT_TIMEOUT_OVERRIDE } = process.env;
// @todo: remove this once https://github.com/woocommerce/woocommerce-admin/issues/6992 has been addressed
addConsoleSuppression( 'woocommerce_shared_settings', false );
/**
* Uses the WordPress API to delete all existing posts
*/
async function trashExistingPosts() {
const apiUrl = config.get('url');
const wpPostsEndpoint = '/wp/v2/posts';
const adminUsername = config.get('users.admin.username');
const adminPassword = config.get('users.admin.password');
const client = HTTPClientFactory.build(apiUrl)
.withBasicAuth(adminUsername, adminPassword)
.create();
// List all existing posts
const response = await client.get(wpPostsEndpoint);
const posts = response.data;
// Delete each post
for (const post of posts) {
await client.delete(`${wpPostsEndpoint}/${post.id}`);
}
}
// Before every test suite run, delete all content created by the test. This ensures
// other posts/comments/etc. aren't dirtying tests and tests don't depend on
// each other's side-effects.
beforeAll(async () => {
if ( DEFAULT_TIMEOUT_OVERRIDE ) {
page.setDefaultNavigationTimeout( DEFAULT_TIMEOUT_OVERRIDE );
page.setDefaultTimeout( DEFAULT_TIMEOUT_OVERRIDE );
}
try {
// Update the ready page to prevent concurrent test runs
await updateReadyPageStatus('draft');
await trashExistingPosts();
await withRestApi.deleteAllProducts();
await withRestApi.deleteAllCoupons();
await withRestApi.deleteAllOrders();
} catch ( error ) {
// Prevent an error here causing tests to fail.
}
await page.goto(WP_ADMIN_LOGIN);
await clearLocalStorage();
await setBrowserViewport( {
width: 1280,
height: 800,
});
});
// Clear browser cookies and cache using DevTools.
// This is to ensure that each test ends with no user logged in.
afterAll(async () => {
// Reset the ready page to published to allow future test runs
try {
await updateReadyPageStatus('publish');
} catch ( error ) {
// Prevent an error here causing tests to fail.
}
const client = await page.target().createCDPSession();
await client.send('Network.clearBrowserCookies');
await client.send('Network.clearBrowserCache');
});

View File

@ -26,14 +26,16 @@
"@slack/web-api": "^6.1.0", "@slack/web-api": "^6.1.0",
"@woocommerce/api": "^0.2.0", "@woocommerce/api": "^0.2.0",
"@wordpress/e2e-test-utils": "^4.16.1", "@wordpress/e2e-test-utils": "^4.16.1",
"@wordpress/jest-preset-default": "^6.4.0", "@wordpress/jest-preset-default": "^7.1.3",
"app-root-path": "^3.0.0", "app-root-path": "^3.0.0",
"commander": "4.1.1", "commander": "4.1.1",
"jest": "^25.1.0", "jest": "^25.1.0",
"jest-each": "25.5.0", "jest-each": "25.5.0",
"jest-puppeteer": "^4.4.0", "jest-puppeteer": "^4.4.0",
"node-stream-zip": "^1.13.6",
"readline-sync": "^1.4.10",
"request": "^2.88.2", "request": "^2.88.2",
"node-stream-zip": "^1.13.6" "sprintf-js": "^1.1.2"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "7.12.8", "@babel/cli": "7.12.8",
@ -42,6 +44,7 @@
"@babel/preset-env": "7.12.7", "@babel/preset-env": "7.12.7",
"@wordpress/eslint-plugin": "7.3.0", "@wordpress/eslint-plugin": "7.3.0",
"eslint": "^8.1.0", "eslint": "^8.1.0",
"jest-circus": "25.1.0",
"ndb": "^1.1.5", "ndb": "^1.1.5",
"semver": "^7.3.2" "semver": "^7.3.2"
}, },
@ -49,6 +52,7 @@
"access": "public" "access": "public"
}, },
"scripts": { "scripts": {
"preinstall": "npx only-allow pnpm",
"clean": "rm -rf ./build ./build-module", "clean": "rm -rf ./build ./build-module",
"compile": "node ./../bin/build.js", "compile": "node ./../bin/build.js",
"build": "pnpm run clean && pnpm run compile", "build": "pnpm run clean && pnpm run compile",

View File

@ -3,6 +3,13 @@
"sourceRoot": "packages/js/e2e-environment/src", "sourceRoot": "packages/js/e2e-environment/src",
"projectType": "library", "projectType": "library",
"targets": { "targets": {
"changelog": {
"executor": "./tools/executors/changelogger:changelog",
"options": {
"action": "add",
"cwd": "packages/js/e2e-environment"
}
},
"build": { "build": {
"executor": "@nrwl/workspace:run-script", "executor": "@nrwl/workspace:run-script",
"options": { "options": {

View File

@ -9,6 +9,7 @@ import {
setBrowserViewport, setBrowserViewport,
} from '@wordpress/e2e-test-utils'; } from '@wordpress/e2e-test-utils';
import { consoleShouldSuppress, addConsoleSuppression } from '../../utils'; import { consoleShouldSuppress, addConsoleSuppression } from '../../utils';
import { setupJestRetries } from '../../config/jest-object.config';
/** /**
* Array of page event tuples of [ eventName, handler ]. * Array of page event tuples of [ eventName, handler ].
@ -175,6 +176,7 @@ beforeAll( async () => {
capturePageEventsForTearDown(); capturePageEventsForTearDown();
enablePageDialogAccept(); enablePageDialogAccept();
observeConsoleLogging(); observeConsoleLogging();
setupJestRetries();
} ); } );
afterEach( async () => { afterEach( async () => {

View File

@ -0,0 +1,104 @@
# WooCommerce End-to-End Test Packages
There are two limitations which significantly impact the architecture of E2E test packages:
- Referencing the `jest` functions `describe`, `it`, `beforeAll`, etc. throws a fatal error outside the `jest` environment.
- `jest` will not scan for tests in any path containing `node_mdules`.
## Creating a tests package
The way to create a tests package with the above limitations is
- **In the tests package**, wrap each test in a function
```js
/**
* Require the necessary jest functions to prevent the package build from referencing them
* `import` references imported functions during package build
*/
const { describe, it, beforeAll } = require( '@jest/globals' );
const testMyCriticalFlow = () => {
describe( 'My Critical Flow', () => {
beforeAll( async () => {
// Test setup
} );
it( 'can complete first step', async () => {
// Do stuff
expect( someValue ).toBeTruthy();
} );
} );
};
modules.exports = testMyFlow;
```
- **In the `tests/e2e/specs` folder**, create a test spec that calls the test function
```js
import { testMyCriticalFlow } from 'MyTestsPackage';
testMyCriticalFlow();
```
## Adding the scaffolds for the test installer
To work with the limitations outlined above, the test installer needs to access the test scaffolding information without accessing the package index. As a result, the `installFiles` is a required path in the steps below
- Create an `installFiles` folder in the root of the package
- Add an `index.js` to the folder which exports an object with some or all of three properties
```js
module.exports = {
defaultJson: 'installFiles/default-test-config.json',
initializeSh: 'installFiles/initialize.sh.default',
testSpecs: 'installFiles/scaffold-tests.json',
};
```
- The value of each of the properties should be a relative path from the package `index.js`. The test installer will remove `dist`, `build`, and `build-modules` from the end of the package index path.
- `defaultJson`: Path to a JSON file containing all `default.json` entries needed for the tests in the package.
- `initializeSh`: Path to a bash script containing the WP CLI commands needed to initialize the `e2e-environment` test container.
- `testSpecs`: Path to a JSON file containing a nested object
```json
{
"active": [
{
"name": "first-folder-name",
"description": "First tests",
"testFiles": [
{
"name": "test-name-a",
"functions": [
"testMyCriticalFlow"
]
},
{
"name": "test-name-b",
"functions": [
"testSecondCriticalFlow",
"testThirdCriticalFlow"
]
}
]
},
{
"name": "second-folder-name",
"description": "Second tests",
"testFiles": [
....
]
}
]
}
```
The test installer uses the `testSpecs` nested object to create test specs. Using the example above, create `tests/e2e/specs/first-folder-name/test-name-b.test.js`:
```js
/* This file was auto-generated by the command `npx wc-e2e install your-package-name`. */
import { testSecondCriticalFlow, testThirdCriticalFlow } from 'your-package-name';
testSecondCriticalFlow();
testThirdCriticalFlow();
```

View File

@ -0,0 +1,5 @@
file_uploads = On
memory_limit = 500M
upload_max_filesize = 500M
post_max_size = 500M
max_execution_time = 600

View File

@ -1,30 +1,27 @@
const path = require( 'path' ); const path = require( 'path' );
const getAppRoot = require( './app-root' );
const fs = require( 'fs' ); const fs = require( 'fs' );
const mkdirp = require( 'mkdirp' ); const mkdirp = require( 'mkdirp' );
const request = require( 'request' ); const request = require( 'request' );
const StreamZip = require( 'node-stream-zip' ); const StreamZip = require( 'node-stream-zip' );
const { resolveLocalE2ePath } = require( './test-config' );
/** /**
* Upload a plugin zip from a remote location, such as a GitHub URL or other hosted location. * Upload a plugin zip from a remote location, such as a GitHub URL or other hosted location.
* *
* @param {string} fileUrl The URL where the zip file is located. * @param {string} fileUrl The URL where the zip file is located.
* @param {string} authorizationToken Authorization token used to authenticate with the GitHub API if required.
* @return {string} The path where the zip file is located. * @return {string} The path where the zip file is located.
*/ */
const getRemotePluginZip = async ( fileUrl ) => { const getRemotePluginZip = async ( fileUrl, authorizationToken = '' ) => {
const appPath = getAppRoot(); const savePath = resolveLocalE2ePath( 'plugins' );
const savePath = path.resolve(
appPath,
'plugins/woocommerce/tests/e2e/plugins'
);
mkdirp.sync( savePath ); mkdirp.sync( savePath );
// Pull the filename from the end of the URL // Pull the version from the end of the URL
const fileName = fileUrl.split( '/' ).pop(); const fileName = fileUrl.split( '/' ).pop();
let filePath = path.join( savePath, fileName ); let filePath = path.join( savePath, fileName );
// First, download the zip file // First, download the zip file
await downloadZip( fileUrl, filePath ); await downloadZip( fileUrl, filePath, authorizationToken );
// Check for a nested zip and update the filepath // Check for a nested zip and update the filepath
filePath = await checkNestedZip( filePath, savePath ); filePath = await checkNestedZip( filePath, savePath );
@ -35,24 +32,24 @@ const getRemotePluginZip = async ( fileUrl ) => {
/** /**
* Get the latest release zip for a plugin from a GiHub repository. * Get the latest release zip for a plugin from a GiHub repository.
* *
* @param {string} owner The owner of the plugin repository. * @param {string} repository The repository owner and name. For example: `woocommerce/woocommerce`.
* @param {string} repository The repository name. * @param {string} authorizationToken Authorization token used to authenticate with the GitHub API if required.
* @param {boolean} getPrerelease Flag on whether to get a prelease or not. * @param {boolean} getPrerelease Flag on whether to get a prelease or not.
* @param {number} perPage Limit of entries returned from the latest releases list, defaults to 3. * @param {number} perPage Limit of entries returned from the latest releases list, defaults to 3.
* @return {Promise<string>}} Returns the URL for the release zip file. * @return {Promise<string>}} Returns the URL for the release zip file.
*/ */
const getLatestReleaseZipUrl = async ( const getLatestReleaseZipUrl = async (
owner,
repository, repository,
authorizationToken = '',
getPrerelease = false, getPrerelease = false,
perPage = 3 perPage = 3
) => { ) => {
let requesturl; let requesturl;
if ( getPrerelease ) { if ( getPrerelease ) {
requesturl = `https://api.github.com/repos/${ owner }/${ repository }/releases?per_page=${ perPage }`; requesturl = `https://api.github.com/repos/${ repository }/releases?per_page=${ perPage }`;
} else { } else {
requesturl = `https://api.github.com/repos/${ owner }/${ repository }/releases/latest`; requesturl = `https://api.github.com/repos/${ repository }/releases/latest`;
} }
const options = { const options = {
@ -62,6 +59,11 @@ const getLatestReleaseZipUrl = async (
headers: { 'user-agent': 'node.js' }, headers: { 'user-agent': 'node.js' },
}; };
// If provided with a token, use it for authorization
if ( authorizationToken ) {
options.headers.Authorization = `token ${ authorizationToken }`;
}
// Wrap in a promise to make the request async // Wrap in a promise to make the request async
return new Promise( function ( resolve, reject ) { return new Promise( function ( resolve, reject ) {
request.get( options, function ( err, resp, body ) { request.get( options, function ( err, resp, body ) {
@ -74,6 +76,12 @@ const getLatestReleaseZipUrl = async (
resolve( release.assets[ 0 ].browser_download_url ); resolve( release.assets[ 0 ].browser_download_url );
} }
} ); } );
} else if ( authorizationToken ) {
// If it's a private repo, we need to download the archive this way
const tagName = body.tag_name;
resolve(
`https://github.com/${ repository }/archive/${ tagName }.zip`
);
} else { } else {
resolve( body.assets[ 0 ].browser_download_url ); resolve( body.assets[ 0 ].browser_download_url );
} }
@ -93,7 +101,7 @@ const checkNestedZip = async ( zipFilePath, savePath ) => {
const entries = await zip.entries(); const entries = await zip.entries();
for ( const entry of Object.values( entries ) ) { for ( const entry of Object.values( entries ) ) {
if ( entry.name.match( /.zip/ ) ) { if ( entry.name.match( /\.zip/ ) ) {
await zip.extract( null, savePath ); await zip.extract( null, savePath );
await zip.close(); await zip.close();
return path.join( savePath, entry.name ); return path.join( savePath, entry.name );
@ -108,15 +116,22 @@ const checkNestedZip = async ( zipFilePath, savePath ) => {
* *
* @param {string} fileUrl The URL where the zip file is located. * @param {string} fileUrl The URL where the zip file is located.
* @param {string} downloadPath The location where to download the zip to. * @param {string} downloadPath The location where to download the zip to.
* @param {string} authorizationToken Authorization token used to authenticate with the GitHub API if required.
* @return {Promise<void>} * @return {Promise<void>}
*/ */
const downloadZip = async ( fileUrl, downloadPath ) => { const downloadZip = async ( fileUrl, downloadPath, authorizationToken ) => {
const options = { const options = {
url: fileUrl, url: fileUrl,
method: 'GET', method: 'GET',
encoding: null, encoding: null,
headers: { 'user-agent': 'node.js' },
}; };
// If provided with a token, use it for authorization
if ( authorizationToken ) {
options.headers.Authorization = `token ${ authorizationToken }`;
}
// Wrap in a promise to make the request async // Wrap in a promise to make the request async
return new Promise( function ( resolve, reject ) { return new Promise( function ( resolve, reject ) {
request request
@ -131,9 +146,27 @@ const downloadZip = async ( fileUrl, downloadPath ) => {
} ); } );
}; };
/**
* Delete the downloaded plugin files.
*/
const deleteDownloadedPluginFiles = async () => {
const pluginSavePath = resolveLocalE2ePath( 'plugins' );
fs.readdir( pluginSavePath, ( err, files ) => {
if ( err ) throw err;
for ( const file of files ) {
fs.unlink( path.join( pluginSavePath, file ), ( error ) => {
if ( error ) throw error;
} );
}
} );
};
module.exports = { module.exports = {
getRemotePluginZip, getRemotePluginZip,
getLatestReleaseZipUrl, getLatestReleaseZipUrl,
checkNestedZip, checkNestedZip,
downloadZip, downloadZip,
deleteDownloadedPluginFiles,
}; };

View File

@ -1,21 +1,24 @@
const getAppRoot = require( './app-root' ); const getAppRoot = require( './app-root' );
const { getAppName, getAppBase } = require( './app-name' ); const { getAppName, getAppBase } = require( './app-name' );
const { getTestConfig, getAdminConfig, resolveLocalE2ePath } = require( './test-config' ); const testConfig = require( './test-config' );
const { getRemotePluginZip, getLatestReleaseZipUrl } = require('./get-plugin-zip'); const {
getRemotePluginZip,
getLatestReleaseZipUrl,
deleteDownloadedPluginFiles,
} = require( './get-plugin-zip' );
const takeScreenshotFor = require( './take-screenshot' ); const takeScreenshotFor = require( './take-screenshot' );
const updateReadyPageStatus = require('./update-ready-page'); const updateReadyPageStatus = require( './update-ready-page' );
const consoleUtils = require( './filter-console' ); const consoleUtils = require( './filter-console' );
module.exports = { module.exports = {
getAppBase, getAppBase,
getAppRoot, getAppRoot,
getAppName, getAppName,
getTestConfig,
getAdminConfig,
resolveLocalE2ePath,
getRemotePluginZip, getRemotePluginZip,
getLatestReleaseZipUrl, getLatestReleaseZipUrl,
deleteDownloadedPluginFiles,
takeScreenshotFor, takeScreenshotFor,
updateReadyPageStatus, updateReadyPageStatus,
...testConfig,
...consoleUtils, ...consoleUtils,
}; };

View File

@ -0,0 +1,124 @@
/**
* External dependencies.
*/
const fs = require( 'fs' );
const path = require( 'path' );
const readlineSync = require( 'readline-sync' );
/**
* Internal dependencies.
*/
const { resolveLocalE2ePath, resolvePackagePath } = require( './test-config' );
/**
* Create a path relative to the local `tests/e2e` folder.
* @param relativePath
* @return {string}
*/
const createLocalE2ePath = ( relativePath ) => {
let specFolderPath = '';
const folders = [ `..${path.sep}..${path.sep}tests`, `..${path.sep}e2e`, relativePath ];
folders.forEach( ( folder ) => {
specFolderPath = resolveLocalE2ePath( folder );
if ( ! fs.existsSync( specFolderPath ) ) {
console.log( `Creating folder ${specFolderPath}` );
fs.mkdirSync( specFolderPath );
}
} );
return specFolderPath;
};
/**
* Prompt the console for confirmation.
*
* @param {string} prompt Prompt for the user.
* @param {string} choices valid responses.
* @return {string}
*/
const confirm = ( prompt, choices ) => {
const answer = readlineSync.keyIn( prompt, choices );
return answer;
};
/**
*
* @param {string} localE2ePath Destination path
* @param {string} packageE2ePath Source path
* @param {string} packageName Source package. Default @woocommerce/e2e-environment package.
* @return {boolean}
*/
const confirmLocalCopy = ( localE2ePath, packageE2ePath, packageName = '' ) => {
const localPath = resolveLocalE2ePath( localE2ePath );
const packagePath = resolvePackagePath( packageE2ePath, packageName );
const confirmPrompt = `${localE2ePath} already exists. Overwrite? [Y]es/[n]o: `;
let overwriteFiles;
if ( fs.existsSync( localPath ) ) {
overwriteFiles = confirm( confirmPrompt, 'ny' );
overwriteFiles = overwriteFiles.toLowerCase();
} else {
overwriteFiles = 'y';
}
if ( overwriteFiles == 'y' ) {
fs.copyFileSync( packagePath, localPath );
return true;
}
return false;
};
/**
* Prompt for confirmation before deleting a local E2E file.
*
* @param {string} localE2ePath Relative path to local E2E file.
*/
const confirmLocalDelete = ( localE2ePath ) => {
const localPath = resolveLocalE2ePath( localE2ePath );
if ( ! fs.existsSync( localPath ) ) {
return;
}
const confirmPrompt = `${localE2ePath} exists. Delete? [y]es/[n]o: `;
const deleteFile = confirm( confirmPrompt, 'ny' );
if ( deleteFile == 'y' ) {
fs.unlinkSync( localPath );
}
};
/**
* Get the install data for a tests package.
*
* @param {string} packageName npm package name
* @return {string}
*/
const getPackageData = ( packageName ) => {
const packageSlug = packageName.replace( '@', '' ).replace( /\//g, '.' );
const installFiles = require( `${packageName}${path.sep}installFiles` );
return { packageSlug, ...installFiles };
};
/**
* Install test runner and test container defaults
*/
const installDefaults = () => {
createLocalE2ePath( 'docker' );
console.log( 'Writing tests/e2e/docker/initialize.sh' );
confirmLocalCopy( `docker${path.sep}initialize.sh`, `installFiles${path.sep}initialize.sh` );
createLocalE2ePath( 'config' );
console.log( 'Writing tests/e2e/config/jest.config.js' );
confirmLocalCopy( `config${path.sep}jest.config.js`, `installFiles${path.sep}jest.config.js` );
console.log( 'Writing tests/e2e/config/jest.setup.js' );
confirmLocalCopy( `config${path.sep}jest.setup.js`, `installFiles${path.sep}jest.setup.js` );
};
module.exports = {
createLocalE2ePath,
confirm,
confirmLocalCopy,
confirmLocalDelete,
getPackageData,
installDefaults,
};

View File

@ -19,15 +19,84 @@ const resolveLocalE2ePath = ( filename = '' ) => {
); );
return resolvedPath; return resolvedPath;
} };
/**
* Resolve a package name installable by npm install.
*
* @param {string} packageName Name of the installed package.
* @param {boolean} allowRecurse Allow a recursive call. Default true.
* @return {object}
*/
const resolvePackage = ( packageName, allowRecurse = true ) => {
const resolvedPackage = {};
try {
const resolvedPath = path.dirname( require.resolve( packageName ) );
const buildPaths = [ 'dist', 'build', 'build-modules' ];
// Remove build paths from the resolved path.
let resolvedParts = resolvedPath.split( path.sep );
for ( let rp = resolvedParts.length - 1; rp >= 0; rp-- ) {
if ( buildPaths.includes( resolvedParts[ rp ] ) ) {
resolvedParts = resolvedParts.slice( 0, -1 );
} else {
break;
}
}
resolvedPackage.path = resolvedParts.join( path.sep );
resolvedPackage.name = packageName;
} catch ( e ) {
// Package name installed is not the package name.
resolvedPackage.path = '';
resolvedPackage.name = '';
}
// Attempt to find the package through the project package lock file.
if ( ! resolvedPackage.path.length && allowRecurse ) {
const packageLockPath = path.resolve( appPath, 'package-lock.json' );
const packageLockContent = fs.readFileSync( packageLockPath );
const { dependencies } = JSON.parse( packageLockContent );
for ( const [ key, value ] of Object.entries( dependencies ) ) {
if ( value.version.indexOf( packageName ) == 0 ) {
resolvedPackage = resolvePackage( key, false );
break;
}
}
}
return resolvedPackage;
};
/**
* Resolve a file in a package.
*
* @param {string} filename Filename to append to the path.
* @param {string} packageName Name of the installed package. Default @woocommerce/e2e-environment.
* @return {string}
*/
const resolvePackagePath = ( filename, packageName = '' ) => {
let packagePath;
if ( ! packageName.length ) {
packagePath = path.resolve( __dirname, '../' );
} else {
const pkg = resolvePackage( packageName );
packagePath = pkg.path;
}
const resolvedPath = path.resolve(
packagePath,
filename.indexOf( '/' ) == 0 ? filename.slice( 1 ) : filename
);
return resolvedPath;
};
// Copy local test configuration file if it exists. // Copy local test configuration file if it exists.
const localTestConfigFile = resolveLocalE2ePath( 'config/default.json' ); const localTestConfigFile = resolveLocalE2ePath( 'config/default.json' );
const defaultConfigFile = path.resolve( const defaultConfigFile = resolvePackagePath( 'config/default/default.json' );
__dirname, const testConfigFile = resolvePackagePath( 'config/default.json' );
'../config/default/default.json'
);
const testConfigFile = path.resolve( __dirname, '../config/default.json' );
if ( fs.existsSync( localTestConfigFile ) ) { if ( fs.existsSync( localTestConfigFile ) ) {
fs.copyFileSync( localTestConfigFile, testConfigFile ); fs.copyFileSync( localTestConfigFile, testConfigFile );
@ -94,4 +163,6 @@ module.exports = {
getTestConfig, getTestConfig,
getAdminConfig, getAdminConfig,
resolveLocalE2ePath, resolveLocalE2ePath,
resolvePackage,
resolvePackagePath,
}; };

View File

@ -1,5 +1,9 @@
# Unreleased # Unreleased
## Fixed
- Identified the default product category using `slug == 'uncategorized'` in `deleteAllProductCategories`
## Changes ## Changes
- Removed `page.waitForNavigation()` from `shopper.logout()` - Removed `page.waitForNavigation()` from `shopper.logout()`

View File

@ -30,6 +30,7 @@
"access": "public" "access": "public"
}, },
"scripts": { "scripts": {
"preinstall": "npx only-allow pnpm",
"clean": "rm -rf ./build ./build-module", "clean": "rm -rf ./build ./build-module",
"compile": "node ./../bin/build.js", "compile": "node ./../bin/build.js",
"build": "pnpm run clean && pnpm run compile", "build": "pnpm run clean && pnpm run compile",

View File

@ -3,6 +3,13 @@
"sourceRoot": "packages/js/e2e-utils/src", "sourceRoot": "packages/js/e2e-utils/src",
"projectType": "library", "projectType": "library",
"targets": { "targets": {
"changelog": {
"executor": "./tools/executors/changelogger:changelog",
"options": {
"action": "add",
"cwd": "packages/js/e2e-utils"
}
},
"build": { "build": {
"executor": "@nrwl/workspace:run-script", "executor": "@nrwl/workspace:run-script",
"options": { "options": {

View File

@ -78,7 +78,7 @@ export const withRestApi = {
}; };
const response = await client.put( onboardingProfileEndpoint, onboardingReset ); const response = await client.put( onboardingProfileEndpoint, onboardingReset );
expect( response.status ).toEqual( 200 ); expect( response.statusCode ).toEqual( 200 );
}, },
/** /**
* Use api package to delete coupons. * Use api package to delete coupons.
@ -138,7 +138,7 @@ export const withRestApi = {
if ( productCategories.data && productCategories.data.length ) { if ( productCategories.data && productCategories.data.length ) {
for ( let c = 0; c < productCategories.data.length; c++ ) { for ( let c = 0; c < productCategories.data.length; c++ ) {
// The default `uncategorized` category can't be deleted // The default `uncategorized` category can't be deleted
if ( productCategories.data[c].id == 0 ) { if ( productCategories.data[c].slug == 'uncategorized' ) {
continue; continue;
} }
const response = await client.delete( productCategoriesPath + `/${productCategories.data[c].id}?force=true` ); const response = await client.delete( productCategoriesPath + `/${productCategories.data[c].id}?force=true` );

View File

@ -1,7 +1,6 @@
/** @format */ /** @format */
module.exports = { module.exports = {
root: true,
env: { env: {
browser: true, browser: true,
es6: true, es6: true,

View File

@ -1,12 +1,3 @@
# Editors
/nbproject/private/
# Grunt
none
# Sass
.sass-cache/
# All CSS # All CSS
/assets/css/** /assets/css/**
/assets/css/*.css /assets/css/*.css
@ -23,7 +14,6 @@ tests/cli/composer.json
tests/cli/vendor tests/cli/vendor
# Unit tests # Unit tests
/tmp
/tests/bin/tmp /tests/bin/tmp
/tests/e2e/config/local-*.json /tests/e2e/config/local-*.json
/tests/e2e/config/local.json /tests/e2e/config/local.json
@ -32,22 +22,6 @@ tests/cli/vendor
/tests/e2e/screenshots /tests/e2e/screenshots
/tests/e2e/plugins /tests/e2e/plugins
# Logs
/logs
# TypeScript files
tsconfig.tsbuildinfo
# Composer
/vendor/
/bin/composer/**/vendor/
/lib/vendor/
contributors.md
contributors.html
# Yarn
yarn.lock
# Packages # Packages
/packages/* /packages/*
!/packages/README.md !/packages/README.md

View File

@ -21,8 +21,8 @@
"pelago/emogrifier": "3.1.0", "pelago/emogrifier": "3.1.0",
"psr/container": "1.0.0", "psr/container": "1.0.0",
"woocommerce/action-scheduler": "3.4.0", "woocommerce/action-scheduler": "3.4.0",
"woocommerce/woocommerce-admin": "2.9.1", "woocommerce/woocommerce-admin": "3.0.0-rc.1",
"woocommerce/woocommerce-blocks": "6.3.3" "woocommerce/woocommerce-blocks": "6.5.0"
}, },
"require-dev": { "require-dev": {
"bamarni/composer-bin-plugin": "^1.4", "bamarni/composer-bin-plugin": "^1.4",

View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "20885acd22c0a58cff8852e7cf4ebf20", "content-hash": "97d29d724f0342a99e7bf3f9d1d3c262",
"packages": [ "packages": [
{ {
"name": "automattic/jetpack-autoloader", "name": "automattic/jetpack-autoloader",
@ -543,16 +543,16 @@
}, },
{ {
"name": "woocommerce/woocommerce-admin", "name": "woocommerce/woocommerce-admin",
"version": "2.9.1", "version": "3.0.0-rc.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/woocommerce/woocommerce-admin.git", "url": "https://github.com/woocommerce/woocommerce-admin.git",
"reference": "fdffbfef084c65a3e2141f0aff41cef3bad27553" "reference": "7c0cdd01ae98be058d684dd19023b0f40094cb63"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/fdffbfef084c65a3e2141f0aff41cef3bad27553", "url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/7c0cdd01ae98be058d684dd19023b0f40094cb63",
"reference": "fdffbfef084c65a3e2141f0aff41cef3bad27553", "reference": "7c0cdd01ae98be058d684dd19023b0f40094cb63",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -608,22 +608,22 @@
"homepage": "https://github.com/woocommerce/woocommerce-admin", "homepage": "https://github.com/woocommerce/woocommerce-admin",
"support": { "support": {
"issues": "https://github.com/woocommerce/woocommerce-admin/issues", "issues": "https://github.com/woocommerce/woocommerce-admin/issues",
"source": "https://github.com/woocommerce/woocommerce-admin/tree/v2.9.1" "source": "https://github.com/woocommerce/woocommerce-admin/tree/v3.0.0-rc.1"
}, },
"time": "2021-12-08T02:59:25+00:00" "time": "2021-12-14T23:55:42+00:00"
}, },
{ {
"name": "woocommerce/woocommerce-blocks", "name": "woocommerce/woocommerce-blocks",
"version": "v6.3.3", "version": "v6.5.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/woocommerce/woocommerce-gutenberg-products-block.git", "url": "https://github.com/woocommerce/woocommerce-gutenberg-products-block.git",
"reference": "38975ad6de9c6a556059c4de6e9ffc586ab82245" "reference": "655a9c1de46262304cc8ac187ca8fcc0abf23b6d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/woocommerce/woocommerce-gutenberg-products-block/zipball/38975ad6de9c6a556059c4de6e9ffc586ab82245", "url": "https://api.github.com/repos/woocommerce/woocommerce-gutenberg-products-block/zipball/655a9c1de46262304cc8ac187ca8fcc0abf23b6d",
"reference": "38975ad6de9c6a556059c4de6e9ffc586ab82245", "reference": "655a9c1de46262304cc8ac187ca8fcc0abf23b6d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -662,9 +662,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues", "issues": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues",
"source": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/tree/v6.3.3" "source": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/tree/v6.5.0"
}, },
"time": "2021-11-25T09:47:27+00:00" "time": "2021-12-07T11:28:05+00:00"
} }
], ],
"packages-dev": [ "packages-dev": [
@ -2926,5 +2926,5 @@
"platform-overrides": { "platform-overrides": {
"php": "7.0.33" "php": "7.0.33"
}, },
"plugin-api-version": "2.0.0" "plugin-api-version": "2.1.0"
} }

View File

@ -514,7 +514,7 @@ $untested_plugins = $plugin_updates->get_untested_plugins( WC()->version, Cons
<tbody> <tbody>
<tr> <tr>
<td data-export-label="WC Database Version"><?php esc_html_e( 'WooCommerce database version', 'woocommerce' ); ?>:</td> <td data-export-label="WC Database Version"><?php esc_html_e( 'WooCommerce database version', 'woocommerce' ); ?>:</td>
<td class="help"><?php echo wc_help_tip( esc_html__( 'The database version for WooCommerce. Note that it may not match WooCommerce core version and that is normal.', 'woocommerce' ) ); /* phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped */ ?></td> <td class="help"><?php echo wc_help_tip( esc_html__( 'The database version for WooCommerce. This should be the same as your WooCommerce version.', 'woocommerce' ) ); /* phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped */ ?></td>
<td><?php echo esc_html( $database['wc_database_version'] ); ?></td> <td><?php echo esc_html( $database['wc_database_version'] ); ?></td>
</tr> </tr>
<tr> <tr>

View File

@ -150,9 +150,8 @@ class WC_Geolocation {
if ( empty( $ip_address ) ) { if ( empty( $ip_address ) ) {
$ip_address = self::get_ip_address(); $ip_address = self::get_ip_address();
}
$country_code = self::get_country_code_from_headers(); $country_code = self::get_country_code_from_headers();
}
/** /**
* Get geolocation filter. * Get geolocation filter.

View File

@ -180,6 +180,7 @@ class WC_Install {
add_action( 'admin_init', array( __CLASS__, 'wc_admin_db_update_notice' ) ); add_action( 'admin_init', array( __CLASS__, 'wc_admin_db_update_notice' ) );
add_action( 'admin_init', array( __CLASS__, 'add_admin_note_after_page_created' ) ); add_action( 'admin_init', array( __CLASS__, 'add_admin_note_after_page_created' ) );
add_action( 'woocommerce_run_update_callback', array( __CLASS__, 'run_update_callback' ) ); add_action( 'woocommerce_run_update_callback', array( __CLASS__, 'run_update_callback' ) );
add_action( 'woocommerce_update_db_to_current_version', array( __CLASS__, 'update_db_version' ) );
add_action( 'admin_init', array( __CLASS__, 'install_actions' ) ); add_action( 'admin_init', array( __CLASS__, 'install_actions' ) );
add_action( 'woocommerce_page_created', array( __CLASS__, 'page_created' ), 10, 2 ); add_action( 'woocommerce_page_created', array( __CLASS__, 'page_created' ), 10, 2 );
add_filter( 'plugin_action_links_' . WC_PLUGIN_BASENAME, array( __CLASS__, 'plugin_action_links' ) ); add_filter( 'plugin_action_links_' . WC_PLUGIN_BASENAME, array( __CLASS__, 'plugin_action_links' ) );
@ -487,6 +488,20 @@ class WC_Install {
} }
} }
} }
// After the callbacks finish, update the db version to the current WC version.
$current_wc_version = WC()->version;
if ( version_compare( $current_db_version, $current_wc_version, '<' ) &&
! WC()->queue()->get_next( 'woocommerce_update_db_to_current_version' ) ) {
WC()->queue()->schedule_single(
time() + $loop,
'woocommerce_update_db_to_current_version',
array(
'version' => $current_wc_version,
),
'woocommerce-db-updates'
);
}
} }
/** /**

View File

@ -782,7 +782,7 @@ class WC_Email extends WC_Settings_API {
protected function save_template( $template_code, $template_path ) { protected function save_template( $template_code, $template_path ) {
if ( current_user_can( 'edit_themes' ) && ! empty( $template_code ) && ! empty( $template_path ) ) { if ( current_user_can( 'edit_themes' ) && ! empty( $template_code ) && ! empty( $template_path ) ) {
$saved = false; $saved = false;
$file = get_stylesheet_directory() . '/' . WC()->template_path() . $template_path; $file = $this->get_theme_template_file( $template_path );
$code = wp_unslash( $template_code ); $code = wp_unslash( $template_code );
if ( is_writeable( $file ) ) { // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_is_writeable if ( is_writeable( $file ) ) { // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_is_writeable

View File

@ -1,3 +1,3 @@
{ {
"extends": "stylelint-config-wordpress", "extends": "@wordpress/stylelint-config",
} }

View File

@ -1,6 +1,6 @@
module.exports = function ( grunt ) { module.exports = function ( grunt ) {
'use strict'; 'use strict';
var sass = require( 'node-sass' ); var sass = require( 'sass' );
grunt.initConfig( { grunt.initConfig( {
// Setting folder templates. // Setting folder templates.

View File

@ -453,12 +453,13 @@ p.demo_store,
td, td,
th { th {
border: 0; border: 0;
vertical-align: top;
line-height: 2em; line-height: 2em;
vertical-align: top;
} }
label { label {
font-weight: 700; font-weight: 700;
text-align: left;
} }
select { select {

View File

@ -807,6 +807,10 @@ jQuery( function( $ ) {
default : default :
$( 'select.variation_actions' ).trigger( do_variation_action ); $( 'select.variation_actions' ).trigger( do_variation_action );
data = $( 'select.variation_actions' ).triggerHandler( do_variation_action + '_ajax_data', data ); data = $( 'select.variation_actions' ).triggerHandler( do_variation_action + '_ajax_data', data );
if ( null === data ) {
return;
}
break; break;
} }

View File

@ -242,8 +242,15 @@
} }
item = $( '<a></a>' ).attr( 'href', '#' ).text( j ); item = $( '<a></a>' ).attr( 'href', '#' ).text( j );
if ( slider.vars.controlNav === "thumbnails" ) { if (slider.vars.controlNav === "thumbnails") {
item = $( '<img/>' ).attr( 'src', slide.attr( 'data-thumb' ) ); item = $('<img/>', {
load: function (el) {
el.currentTarget.width = el.currentTarget.naturalWidth;
el.currentTarget.height = el.currentTarget.naturalHeight;
},
src: slide.attr('data-thumb'),
alt: slide.attr('alt')
})
} }
if ( '' !== slide.attr( 'data-thumb-alt' ) ) { if ( '' !== slide.attr( 'data-thumb-alt' ) ) {

View File

@ -6,6 +6,7 @@
"private": true, "private": true,
"main": "Gruntfile.js", "main": "Gruntfile.js",
"devDependencies": { "devDependencies": {
"@wordpress/stylelint-config": "19.1.0",
"autoprefixer": "9.8.6", "autoprefixer": "9.8.6",
"browserslist": "4.14.5", "browserslist": "4.14.5",
"caniuse-lite": "1.0.30001146", "caniuse-lite": "1.0.30001146",
@ -24,8 +25,7 @@
"grunt-sass": "3.1.0", "grunt-sass": "3.1.0",
"grunt-stylelint": "0.16.0", "grunt-stylelint": "0.16.0",
"gruntify-eslint": "5.0.0", "gruntify-eslint": "5.0.0",
"node-sass": "6.0.1", "sass": "^1.45.0",
"stylelint": "13.8.0", "stylelint": "13.8.0"
"stylelint-config-wordpress": "17.0.0"
} }
} }

View File

@ -33,7 +33,7 @@
"devDependencies": { "devDependencies": {
"@babel/cli": "7.12.8", "@babel/cli": "7.12.8",
"@babel/core": "7.12.9", "@babel/core": "7.12.9",
"@babel/polyfill": "7.12.1", "babel-eslint": "10.1.0",
"@babel/preset-env": "7.12.7", "@babel/preset-env": "7.12.7",
"@babel/register": "7.12.1", "@babel/register": "7.12.1",
"@typescript-eslint/eslint-plugin": "3.10.1", "@typescript-eslint/eslint-plugin": "3.10.1",
@ -48,8 +48,8 @@
"@wordpress/babel-plugin-import-jsx-pragma": "1.1.3", "@wordpress/babel-plugin-import-jsx-pragma": "1.1.3",
"@wordpress/babel-preset-default": "3.0.2", "@wordpress/babel-preset-default": "3.0.2",
"@wordpress/eslint-plugin": "7.3.0", "@wordpress/eslint-plugin": "7.3.0",
"@wordpress/stylelint-config": "19.1.0",
"autoprefixer": "9.8.6", "autoprefixer": "9.8.6",
"babel-eslint": "10.1.0",
"chai": "4.2.0", "chai": "4.2.0",
"chai-as-promised": "7.1.1", "chai-as-promised": "7.1.1",
"config": "3.3.3", "config": "3.3.3",
@ -64,18 +64,16 @@
"jest": "^25.1.0", "jest": "^25.1.0",
"lint-staged": "9.5.0", "lint-staged": "9.5.0",
"mocha": "7.2.0", "mocha": "7.2.0",
"node-sass": "6.0.1",
"prettier": "npm:wp-prettier@2.0.5", "prettier": "npm:wp-prettier@2.0.5",
"stylelint": "^13.8.0", "stylelint": "^13.8.0",
"stylelint-config-wordpress": "17.0.0",
"typescript": "3.9.7", "typescript": "3.9.7",
"webpack": "4.44.2", "webpack": "4.44.2",
"webpack-cli": "3.3.12", "webpack-cli": "3.3.12",
"wp-textdomain": "1.0.1" "wp-textdomain": "1.0.1"
}, },
"engines": { "engines": {
"node": "^12.20.1", "node": "^16.13.1",
"npm": "^6.14.6" "npm": "^8.1.2"
}, },
"husky": { "husky": {
"hooks": { "hooks": {

View File

@ -4,6 +4,13 @@
"projectType": "application", "projectType": "application",
"implicitDependencies": [ "woocommerce-legacy-assets" ], "implicitDependencies": [ "woocommerce-legacy-assets" ],
"targets": { "targets": {
"changelog": {
"executor": "./tools/executors/changelogger:changelog",
"options": {
"action": "add",
"cwd": "plugins/woocommerce"
}
},
"composer-install": { "composer-install": {
"executor": "@nrwl/workspace:run-commands", "executor": "@nrwl/workspace:run-commands",
"options": { "options": {

View File

@ -4,7 +4,7 @@ Tags: e-commerce, store, sales, sell, woo, shop, cart, checkout, downloadable, d
Requires at least: 5.6 Requires at least: 5.6
Tested up to: 5.8 Tested up to: 5.8
Requires PHP: 7.0 Requires PHP: 7.0
Stable tag: 5.9.0 Stable tag: 6.0.0
License: GPLv3 License: GPLv3
License URI: https://www.gnu.org/licenses/gpl-3.0.html License URI: https://www.gnu.org/licenses/gpl-3.0.html
@ -160,61 +160,6 @@ WooCommerce comes with some sample data you can use to see how products look; im
== Changelog == == Changelog ==
= 5.9.0 2021-11-09 = = 6.1.0 2021-XX-XX =
**WooCommerce**
* Fix - Bug in the handling of remote file names for downloadable files.
* Fix - Remove the absolute path to the currency-info.php from within locale-info.php. #31036
* Fix - wc_get_price_excluding_tax when an order with no customer is passed. #31015
* Fix - Rename transient used to cache data for Featured page of In-App Marketplace. #31002
* Fix - Variable product price caching bug with VAT exemption. #30889
* Fix - Allow to pass null as the email for billing addresses in REST API. #30850
* Fix - Ensure woocommerce_cancel_unpaid_orders event is always re-scheduled. #30830
* Fix - Use a more standard way to check if the product attributes lookup table exists. #30745
* Fix - Undefined variable notice when trying to add product in orders without specifying a product. #30739
* Fix - Use proper location for taxes when adding products via admin. #30692
* Dev - Add mobile data to WCTracker. #30415
* Tweak - Remove hardcode category banners in Settings > Marketplace and use the WooCommerce.com API instead. #30938
* Tweak - Show a search again message when marketplace results are empty. #30642
* Tweak - Add promoted cards styling to marketplace section. #30861
* Enhancement - Add ratings, reviews and icons into Marketplace's Product Cards. #30840
* Enhancement - Update Storefront banner width and track links in the marketplace page. #30882
* Enhancement - Revamp the WooCommerce Marketplace page. #30900
**WooCommerce Admin - 2.8.0 **
* Fix - Issue where stock activity panel was not rendering correctly. #7817
* Fix - Increase CSS specificity to avoid conflicts and broken panel styling. #7813
* Fix - Updated link to WooCommerce Developers Blog in readme.txt. #7824
* Fix - Fixed navigation menu text color after Gutenberg 11.6.0. #7771
* Fix - Add status param to notes/delete/all REST endpoint, to correctly delete all notes. #7743
* Fix - Allow already installed marketing extensions to be activated. #7740
* Fix - Add missing title text for marketing task. #7640
* Fix - Assign parent order status as children order status if refund order. #7253
* Fix - Fix category lookup logic to update children correctly. #7709
* Fix - Fixing an unwanted page refresh when using Woo Navigation. #7615
* Fix - Fix naming of event names and properties. #7677
* Fix - Fix white screen for variation analytic data without a name. #7686
* Add - Store Profiler and Product task - include Subscriptions. #7734
* Update - Update WC pay supported country list for the default free extensions. #7873
* Update - Update back up copy of free extension for Google Listing & Ads plugin. #7798
* Update - Update Eway payment gateway capitalization (was eWAY). #7678
* Update - Enable Square in France. #7679
* Enhancement - Only load tasks during rest api requests. #7856
* Enhancement - Add experiment for promoting WooCommerce Payments in payment methods table. #7666
**WooCommerce Blocks - 6.0.0 & 6.0.1 & 6.0.2 & 6.1.0**
* Fix - Infinite recursion when removing an attribute filter from the Active filters block. #4816
* Fix - Update All Reviews block so it honors 'ratings enabled' and 'show avatars' preferences. #4764
* Fix - Products by Category: Moved renderEmptyResponsePlaceholder to separate method to prevent unnecessary rerender. #4751
* Fix - Calculation of number of reviews in the Reviews by Category block. #4729
* Fix - Dropdown list in Product Category List Block for nested categories #4920
* Fix - String translations within the All Products Block. #4897
* Fix - Filter By Price: Update aria values to be more representative of the actual values presented. #4839
* Fix - Filter button from Filter Products by Attribute block is not aligned with the input field. #4814
* Fix - Remove IntersectionObserver shim in favor of dropping IE11 support. #4808
* Enhancement - Added global styles to All Reviews, Reviews by Category and Reviews by Product blocks. Now it's possible to change the text color and font size of those blocks. #4323
[See changelog for all versions](https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/changelog.txt). [See changelog for all versions](https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/changelog.txt).

View File

@ -12,7 +12,7 @@
* *
* @see https://docs.woocommerce.com/document/template-structure/ * @see https://docs.woocommerce.com/document/template-structure/
* @package WooCommerce\Templates * @package WooCommerce\Templates
* @version 3.5.5 * @version 6.1.0
*/ */
defined( 'ABSPATH' ) || exit; defined( 'ABSPATH' ) || exit;
@ -35,7 +35,7 @@ do_action( 'woocommerce_before_add_to_cart_form' ); ?>
<tbody> <tbody>
<?php foreach ( $attributes as $attribute_name => $options ) : ?> <?php foreach ( $attributes as $attribute_name => $options ) : ?>
<tr> <tr>
<td class="label"><label for="<?php echo esc_attr( sanitize_title( $attribute_name ) ); ?>"><?php echo wc_attribute_label( $attribute_name ); // WPCS: XSS ok. ?></label></td> <th class="label"><label for="<?php echo esc_attr( sanitize_title( $attribute_name ) ); ?>"><?php echo wc_attribute_label( $attribute_name ); // WPCS: XSS ok. ?></label></th>
<td class="value"> <td class="value">
<?php <?php
wc_dropdown_variation_attribute_options( wc_dropdown_variation_attribute_options(

View File

@ -186,6 +186,15 @@ For example:
- `PUPPETEER_SLOWMO=10` - will run tests faster - `PUPPETEER_SLOWMO=10` - will run tests faster
- `PUPPETEER_SLOWMO=70` - will run tests slower - `PUPPETEER_SLOWMO=70` - will run tests slower
### How to retry failed tests
Sometimes tests may fail for different reasons such as network issues, or lost connection. To mitigate against test flakiess, failed tests are rerun up to 3 times before being marked as failed. The amount of retry attempts can be adjusted by passing the `E2E_RETRY_TIMES` variable when running tests. For example:
```
cd plugins/woocommerce
E2E_RETRY_TIMES=2 pnpx wc-e2e test:e2e
```
### How to run tests in debug mode ### How to run tests in debug mode
Tests run in headless mode by default. While writing tests it may be useful to have the debugger loaded while running a test in non-headless mode. To run tests in debug mode: Tests run in headless mode by default. While writing tests it may be useful to have the debugger loaded while running a test in non-headless mode. To run tests in debug mode:

View File

@ -7,7 +7,7 @@ import {
const config = require( 'config' ); const config = require( 'config' );
const { HTTPClientFactory } = require( '@woocommerce/api' ); const { HTTPClientFactory } = require( '@woocommerce/api' );
const { addConsoleSuppression, updateReadyPageStatus } = require( '@woocommerce/e2e-environment' ); const { addConsoleSuppression, updateReadyPageStatus, setupJestRetries } = require( '@woocommerce/e2e-environment' );
const { DEFAULT_TIMEOUT_OVERRIDE } = process.env; const { DEFAULT_TIMEOUT_OVERRIDE } = process.env;
// @todo: remove this once https://github.com/woocommerce/woocommerce-admin/issues/6992 has been addressed // @todo: remove this once https://github.com/woocommerce/woocommerce-admin/issues/6992 has been addressed
@ -40,6 +40,8 @@ async function trashExistingPosts() {
// each other's side-effects. // each other's side-effects.
beforeAll(async () => { beforeAll(async () => {
setupJestRetries( 2 );
if ( DEFAULT_TIMEOUT_OVERRIDE ) { if ( DEFAULT_TIMEOUT_OVERRIDE ) {
page.setDefaultNavigationTimeout( DEFAULT_TIMEOUT_OVERRIDE ); page.setDefaultNavigationTimeout( DEFAULT_TIMEOUT_OVERRIDE );
page.setDefaultTimeout( DEFAULT_TIMEOUT_OVERRIDE ); page.setDefaultTimeout( DEFAULT_TIMEOUT_OVERRIDE );

View File

@ -3,7 +3,7 @@
*/ */
const { merchant, utils } = require( '@woocommerce/e2e-utils' ); const { merchant, utils } = require( '@woocommerce/e2e-utils' );
const { getRemotePluginZip, getLatestReleaseZipUrl } = require( '@woocommerce/e2e-environment' ); const { getRemotePluginZip, getLatestReleaseZipUrl, deleteDownloadedPluginFiles } = require( '@woocommerce/e2e-environment' );
/** /**
* External dependencies * External dependencies
@ -24,7 +24,7 @@ utils.describeIf( UPDATE_WC )( 'WooCommerce plugin can be uploaded and activated
beforeAll( async () => { beforeAll( async () => {
if ( TEST_RELEASE ) { if ( TEST_RELEASE ) {
zipUrl = await getLatestReleaseZipUrl( 'woocommerce', 'woocommerce' ); zipUrl = await getLatestReleaseZipUrl( 'woocommerce/woocommerce' );
} else { } else {
zipUrl = 'https://github.com/woocommerce/woocommerce/releases/download/nightly/woocommerce-trunk-nightly.zip'; zipUrl = 'https://github.com/woocommerce/woocommerce/releases/download/nightly/woocommerce-trunk-nightly.zip';
} }
@ -35,6 +35,7 @@ utils.describeIf( UPDATE_WC )( 'WooCommerce plugin can be uploaded and activated
afterAll( async () => { afterAll( async () => {
await merchant.logout(); await merchant.logout();
await deleteDownloadedPluginFiles();
}); });
it( 'can upload and activate the WooCommerce plugin', async () => { it( 'can upload and activate the WooCommerce plugin', async () => {
@ -46,4 +47,8 @@ utils.describeIf( UPDATE_WC )( 'WooCommerce plugin can be uploaded and activated
await merchant.runDatabaseUpdate(); await merchant.runDatabaseUpdate();
}); });
it( 'can remove downloaded plugin zip', async () => {
await deleteDownloadedPluginFiles();
} );
}); });

View File

@ -0,0 +1,42 @@
/**
* Internal dependencies
*/
const { merchant, utils } = require( '@woocommerce/e2e-utils' );
const { getRemotePluginZip, getLatestReleaseZipUrl, deleteDownloadedPluginFiles } = require( '@woocommerce/e2e-environment' );
/**
* External dependencies
*/
const {
it,
beforeAll,
} = require( '@jest/globals' );
const { GITHUB_REPOSITORY, PLUGIN_NAME, GITHUB_TOKEN } = process.env;
let zipUrl;
let pluginPath;
utils.describeIf( GITHUB_REPOSITORY )( 'Upload and activate plugin', () => {
beforeAll( async () => {
zipUrl = await getLatestReleaseZipUrl( GITHUB_REPOSITORY, GITHUB_TOKEN );
pluginPath = await getRemotePluginZip( zipUrl, GITHUB_TOKEN );
await merchant.login();
});
afterAll( async () => {
await merchant.logout();
});
it( 'can upload and activate the provided plugin', async () => {
await merchant.uploadAndActivatePlugin( pluginPath, PLUGIN_NAME );
});
it( 'can remove downloaded plugin zip', async () => {
await deleteDownloadedPluginFiles();
} );
});

View File

@ -82,6 +82,7 @@ product_sku | SKU of product to be used in cart and checkout flow | yes `__ENV.P
product_url | the `product-name` portion of product permalink of the product to be used in cart and checkout flow | yes `__ENV.P_URL` product_url | the `product-name` portion of product permalink of the product to be used in cart and checkout flow | yes `__ENV.P_URL`
product_id | the product ID of of product to be used in cart and checkout flow | yes `__ENV.P_ID` product_id | the product ID of of product to be used in cart and checkout flow | yes `__ENV.P_ID`
product_search_term | search term to return product to be used in cart and checkout flow | yes `__ENV.P_TERM` product_search_term | search term to return product to be used in cart and checkout flow | yes `__ENV.P_TERM`
product_category | category of product to be used for browsing category products | yes `__ENV.P_CAT`
coupon_code | coupon code to be used in applying coupon flow | yes `__ENV.P_COUPON` coupon_code | coupon code to be used in applying coupon flow | yes `__ENV.P_COUPON`
add_product_title | title of product to be added in merchant add product flow | no add_product_title | title of product to be added in merchant add product flow | no
add_product_regular_price | regular price of product to be added in merchant add product flow | no add_product_regular_price | regular price of product to be added in merchant add product flow | no
@ -102,7 +103,7 @@ To execute an individual test file (for example `requests/shopper/shop-page.js`)
CLI `k6 run requests/shopper/shop-page.js` CLI `k6 run requests/shopper/shop-page.js`
Docker `docker run --network="host" -v /[YOUR LOCAL WC DIRECTORY FULL PATH]/requests:/requests -it loadimpact/k6 run /requests/shopper/shop-page.js` Docker `docker run --network="host" -v /[YOUR LOCAL WC DIRECTORY FULL PATH]/tests:/tests -it loadimpact/k6 run /tests/performance/requests/shopper/shop-page.js`
This will run the individual test for 1 iteration. This will run the individual test for 1 iteration.

View File

@ -37,6 +37,7 @@ export const product_sku = __ENV.P_SKU || 'woo-beanie';
export const product_url = __ENV.P_URL || 'beanie'; export const product_url = __ENV.P_URL || 'beanie';
export const product_id = __ENV.P_ID || '13'; export const product_id = __ENV.P_ID || '13';
export const product_search_term = __ENV.P_TERM || 'beanie'; export const product_search_term = __ENV.P_TERM || 'beanie';
export const product_category = __ENV.P_CAT || 'Accessories';
export const coupon_code = __ENV.P_COUPON || 'testing'; export const coupon_code = __ENV.P_COUPON || 'testing';

View File

@ -0,0 +1,45 @@
import { sleep, check, group } from "k6";
import http from "k6/http";
import { Trend } from "k6/metrics";
import { randomIntBetween } from "https://jslib.k6.io/k6-utils/1.1.0/index.js";
import { base_url, think_time_min, think_time_max, product_category } from "../../config.js";
import {
htmlRequestHeader,
commonRequestHeaders,
commonGetRequestHeaders,
commonNonStandardHeaders,
} from "../../headers.js";
// Custom metric to add to standard results output.
let categoryPageTrend = new Trend("wc_get_product_category_name");
export function categoryPage() {
let response;
group("Category Page", function () {
var requestHeaders = Object.assign({},
htmlRequestHeader,
commonRequestHeaders,
commonGetRequestHeaders,
commonNonStandardHeaders
);
response = http.get(`${base_url}/product-category/${product_category}/`, {
headers: requestHeaders,
});
categoryPageTrend.add(response.timings.duration);
check(response, {
"is status 200": (r) => r.status === 200,
"body contains: Category's title": (response) =>
response.body.includes(
`<h1 class="woocommerce-products-header__title page-title">${product_category}</h1>`
),
});
});
sleep(randomIntBetween(`${think_time_min}`, `${think_time_max}`));
}
export default function () {
categoryPage();
}

View File

@ -15,6 +15,7 @@ import { orders } from '../requests/merchant/orders.js';
import { ordersHeartbeat } from '../requests/merchant/orders-heartbeat.js'; import { ordersHeartbeat } from '../requests/merchant/orders-heartbeat.js';
import { homeWCAdmin } from '../requests/merchant/home-wc-admin.js'; import { homeWCAdmin } from '../requests/merchant/home-wc-admin.js';
import { myAccountMerchantLogin } from '../requests/merchant/my-account-merchant.js'; import { myAccountMerchantLogin } from '../requests/merchant/my-account-merchant.js';
import { categoryPage } from '../requests/shopper/category-page.js';
export let options = { export let options = {
scenarios: { scenarios: {
@ -124,6 +125,7 @@ export function shopperBrowsingFlows() {
cartRemoveItem(); cartRemoveItem();
cartApplyCoupon(); cartApplyCoupon();
myAccount(); myAccount();
categoryPage();
} }
export function checkoutGuestFlow() { export function checkoutGuestFlow() {
cart(); cart();

View File

@ -15,6 +15,7 @@ import { orders } from '../requests/merchant/orders.js';
import { ordersHeartbeat } from '../requests/merchant/orders-heartbeat.js'; import { ordersHeartbeat } from '../requests/merchant/orders-heartbeat.js';
import { homeWCAdmin } from '../requests/merchant/home-wc-admin.js'; import { homeWCAdmin } from '../requests/merchant/home-wc-admin.js';
import { myAccountMerchantLogin } from '../requests/merchant/my-account-merchant.js'; import { myAccountMerchantLogin } from '../requests/merchant/my-account-merchant.js';
import { categoryPage } from '../requests/shopper/category-page.js';
export let options = { export let options = {
scenarios: { scenarios: {
@ -114,6 +115,7 @@ export function shopperBrowsingFlows() {
cartRemoveItem(); cartRemoveItem();
cartApplyCoupon(); cartApplyCoupon();
myAccount(); myAccount();
categoryPage();
} }
export function checkoutGuestFlow() { export function checkoutGuestFlow() {
cart(); cart();

View File

@ -8,6 +8,7 @@ import { cartApplyCoupon } from '../requests/shopper/cart-apply-coupon.js';
import { checkoutGuest } from '../requests/shopper/checkout-guest.js'; import { checkoutGuest } from '../requests/shopper/checkout-guest.js';
import { checkoutCustomerLogin } from '../requests/shopper/checkout-customer-login.js'; import { checkoutCustomerLogin } from '../requests/shopper/checkout-customer-login.js';
import { myAccount } from '../requests/shopper/my-account.js'; import { myAccount } from '../requests/shopper/my-account.js';
import { categoryPage } from '../requests/shopper/category-page.js';
export let options = { export let options = {
scenarios: { scenarios: {
@ -88,6 +89,7 @@ export function searchProductFlow() {
} }
export function singleProductFlow() { export function singleProductFlow() {
singleProduct(); singleProduct();
categoryPage();
} }
export function checkoutGuestFlow() { export function checkoutGuestFlow() {
cart(); cart();

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,9 @@
{
"executors": {
"changelog": {
"implementation": "./impl",
"schema": "./schema.json",
"description": "Executes Jetpack Changelogger scripts."
}
}
}

View File

@ -0,0 +1,115 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
exports.__esModule = true;
var child_process_1 = require("child_process");
var path_1 = require("path");
var fs_1 = require("fs");
var chalk = require("chalk");
var changeloggerScriptPath = 'vendor/bin/changelogger';
function runChangelogger(_a) {
var action = _a.action, cwd = _a.cwd, extraArgs = __rest(_a, ["action", "cwd"]);
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_b) {
return [2 /*return*/, new Promise(function (resolve, reject) {
var args = [action].concat(
// Add any extra arguments supplied. NX camel cases and converts values to Numbers.
// Undo all that so arguments can be passed to Jetpack Changelogger unmodified.
Object.keys(extraArgs).map(function (key) {
return "--" + key.replace(/[A-Z]/g, function (m) { return '-' + m.toLowerCase(); }) + "=" + (Number(extraArgs[key]) && action === 'write'
? extraArgs[key].toFixed(1)
: extraArgs[key]);
}));
var changeloggerScript = (0, child_process_1.spawn)("./" + changeloggerScriptPath, args, {
stdio: 'inherit'
});
changeloggerScript.on('close', function (code) {
resolve({ code: code, error: undefined });
});
changeloggerScript.on('error', function (error) {
reject({ code: 1, error: error });
});
})];
});
});
}
function changelogExecutor(options, context) {
return __awaiter(this, void 0, void 0, function () {
var cwd, projectPath, _a, code, error;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
cwd = options.cwd;
projectPath = (0, path_1.join)(__dirname, '../../../', cwd);
console.info(chalk.cyan("\nExecuting Changelogger...\n"));
try {
process.chdir(projectPath);
console.log(chalk.yellow('Executing from directory: ' + process.cwd() + '\n'));
}
catch (error) {
console.error(chalk.bgRed('Unable to find project working directory'));
console.error(error);
return [2 /*return*/, { success: false }];
}
if (!(0, fs_1.existsSync)(changeloggerScriptPath)) {
console.error(chalk.bgRed('Changelogger scripts not found. Did you remember to `composer install` from project directory?'));
return [2 /*return*/, { success: false }];
}
return [4 /*yield*/, runChangelogger(options)];
case 1:
_a = _b.sent(), code = _a.code, error = _a.error;
if (error) {
console.error(chalk.bgRed(error));
return [2 /*return*/, { success: false }];
}
return [2 /*return*/, { success: code === 0 }];
}
});
});
}
exports["default"] = changelogExecutor;

View File

@ -0,0 +1,96 @@
/**
* External dependencies
*/
import { ExecutorContext } from '@nrwl/devkit';
import { spawn } from 'child_process';
import { join } from 'path';
import { existsSync } from 'fs';
import * as chalk from 'chalk';
export interface ChangelogExecutorOptions {
action: string;
cwd: string;
}
const changeloggerScriptPath = 'vendor/bin/changelogger';
async function runChangelogger( {
action,
cwd,
...extraArgs
}: ChangelogExecutorOptions ): Promise< { code: number; error: string } > {
return new Promise( ( resolve, reject ) => {
const args = [ action ].concat(
// Add any extra arguments supplied. NX camel cases and converts values to Numbers.
// Undo all that so arguments can be passed to Jetpack Changelogger unmodified.
Object.keys( extraArgs ).map(
( key ) =>
`--${ key.replace(
/[A-Z]/g,
( m ) => '-' + m.toLowerCase()
) }=${
Number( extraArgs[ key ] ) && action === 'write'
? extraArgs[ key ].toFixed( 1 )
: extraArgs[ key ]
}`
)
);
const changeloggerScript = spawn(
`./${ changeloggerScriptPath }`,
args,
{
stdio: 'inherit',
}
);
changeloggerScript.on( 'close', ( code ) => {
resolve( { code, error: undefined } );
} );
changeloggerScript.on( 'error', ( error ) => {
reject( { code: 1, error } );
} );
} );
}
export default async function changelogExecutor(
options: ChangelogExecutorOptions,
context: ExecutorContext
) {
const { cwd } = options;
const projectPath = join( __dirname, '../../../', cwd );
console.info( chalk.cyan( `\nExecuting Changelogger...\n` ) );
try {
process.chdir( projectPath );
console.log(
chalk.yellow( 'Executing from directory: ' + process.cwd() + '\n' )
);
} catch ( error ) {
console.error(
chalk.bgRed( 'Unable to find project working directory' )
);
console.error( error );
return { success: false };
}
if ( ! existsSync( changeloggerScriptPath ) ) {
console.error(
chalk.bgRed(
'Changelogger scripts not found. Did you remember to `composer install` from project directory?'
)
);
return { success: false };
}
const { code, error } = await runChangelogger( options );
if ( error ) {
console.error( chalk.bgRed( error ) );
return { success: false };
}
return { success: code === 0 };
}

View File

@ -0,0 +1,3 @@
{
"executors": "./executor.json"
}

View File

@ -0,0 +1,13 @@
{
"cli": "nx",
"id": "Changelogger",
"description": "Executes Jetpack Changelogger scripts.",
"type": "object",
"properties": {
"action": {
"type": "string",
"description": "Action to take"
}
},
"required": [ "action" ]
}