merge trunk
This commit is contained in:
commit
9675b7ac15
|
@ -26,7 +26,7 @@ If you have questions about the process to contribute code or want to discuss de
|
|||
- [Minification of SCSS and JS](https://github.com/woocommerce/woocommerce/wiki/Minification-of-SCSS-and-JS)
|
||||
- [Naming conventions](https://github.com/woocommerce/woocommerce/wiki/Naming-conventions)
|
||||
- [String localisation guidelines](https://github.com/woocommerce/woocommerce/wiki/String-localisation-guidelines)
|
||||
- [Running unit tests](https://github.com/woocommerce/woocommerce/blob/master/tests/README.md)
|
||||
- [Running unit tests](https://github.com/woocommerce/woocommerce/blob/trunk/tests/README.md)
|
||||
- [Running e2e tests](https://github.com/woocommerce/woocommerce/wiki/End-to-end-Testing)
|
||||
|
||||
## Coding Guidelines and Development 🛠
|
||||
|
@ -37,7 +37,7 @@ If you have questions about the process to contribute code or want to discuss de
|
|||
- Ensure you use LF line endings in your code editor. Use [EditorConfig](http://editorconfig.org/) if your editor supports it so that indentation, line endings and other settings are auto configured.
|
||||
- When committing, reference your issue number (#1234) and include a note about the fix.
|
||||
- Ensure that your code supports the minimum supported versions of PHP and WordPress; this is shown at the top of the `readme.txt` file.
|
||||
- Push the changes to your fork and submit a pull request on the master branch of the WooCommerce repository.
|
||||
- Push the changes to your fork and submit a pull request on the trunk branch of the WooCommerce repository.
|
||||
- Make sure to write good and detailed commit messages (see [this post](https://chris.beams.io/posts/git-commit/) for more on this) and follow all the applicable sections of the pull request template.
|
||||
- Please avoid modifying the changelog directly or updating the .pot files. These will be updated by the WooCommerce team.
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
### All Submissions:
|
||||
|
||||
* [ ] Have you followed the [WooCommerce Contributing guideline](https://github.com/woocommerce/woocommerce/blob/master/.github/CONTRIBUTING.md)?
|
||||
* [ ] Have you followed the [WooCommerce Contributing guideline](https://github.com/woocommerce/woocommerce/blob/trunk/.github/CONTRIBUTING.md)?
|
||||
* [ ] Does your code follow the [WordPress' coding standards](https://make.wordpress.org/core/handbook/best-practices/coding-standards/)?
|
||||
* [ ] Have you checked to ensure there aren't other open [Pull Requests](../../pulls) for the same update/change?
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build: [master]
|
||||
build: [trunk]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
|
|
|
@ -21,19 +21,6 @@ jobs:
|
|||
name: woocommerce
|
||||
path: ${{ steps.build.outputs.zip_path }}
|
||||
retention-days: 7
|
||||
|
||||
- name: Add comment
|
||||
uses: actions/github-script@v3
|
||||
if: github.repository_owner == 'woocommerce'
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
github.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: ':package: Artifacts ready for [download](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})!'
|
||||
})
|
||||
|
||||
e2e-tests-cache:
|
||||
name: Set e2e caches for running tests
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
version: ~> 1.0
|
||||
|
||||
# Specifies that Travis should create builds for master and release branches and also tags.
|
||||
# Specifies that Travis should create builds for trunk and release branches and also tags.
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- trunk
|
||||
- /^\d+\.\d+(\.\d+)?(-\S*)?$/
|
||||
- /^release\//
|
||||
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
<a href="https://packagist.org/packages/woocommerce/woocommerce"><img src="https://poser.pugx.org/woocommerce/woocommerce/v/stable" alt="Latest Stable Version"></a>
|
||||
<img src="https://img.shields.io/wordpress/plugin/dt/woocommerce.svg" alt="WordPress.org downloads">
|
||||
<img src="https://img.shields.io/wordpress/plugin/r/woocommerce.svg" alt="WordPress.org rating">
|
||||
<a href="https://travis-ci.com/woocommerce/woocommerce"><img src="https://travis-ci.com/woocommerce/woocommerce.svg?branch=master" alt="Build Status"></a>
|
||||
<a href="https://codecov.io/gh/woocommerce/woocommerce"><img src="https://codecov.io/gh/woocommerce/woocommerce/branch/master/graph/badge.svg" alt="codecov"></a>
|
||||
<a href="https://travis-ci.com/woocommerce/woocommerce"><img src="https://travis-ci.com/woocommerce/woocommerce.svg?branch=trunk" alt="Build Status"></a>
|
||||
<a href="https://codecov.io/gh/woocommerce/woocommerce"><img src="https://codecov.io/gh/woocommerce/woocommerce/branch/trunk/graph/badge.svg" alt="codecov"></a>
|
||||
</p>
|
||||
|
||||
Welcome to the WooCommerce repository on GitHub. Here you can browse the source, look at open issues and keep track of development. We recommend all developers to follow the [WooCommerce development blog](https://woocommerce.wordpress.com/) to stay up to date about everything happening in the project. You can also [follow @DevelopWC](https://twitter.com/DevelopWC) on Twitter for the latest development updates.
|
||||
|
@ -34,4 +34,4 @@ This repository is not suitable for support. Please don't use our issue tracker
|
|||
Support requests in issues on this repository will be closed on sight.
|
||||
|
||||
## Contributing to WooCommerce
|
||||
If you have a patch or have stumbled upon an issue with WooCommerce core, you can contribute this back to the code. Please read our [contributor guidelines](https://github.com/woocommerce/woocommerce/blob/master/.github/CONTRIBUTING.md) for more information how you can do this.
|
||||
If you have a patch or have stumbled upon an issue with WooCommerce core, you can contribute this back to the code. Please read our [contributor guidelines](https://github.com/woocommerce/woocommerce/blob/trunk/.github/CONTRIBUTING.md) for more information how you can do this.
|
||||
|
|
|
@ -1,33 +1,90 @@
|
|||
/*global wc_geolocation_params */
|
||||
jQuery( function( $ ) {
|
||||
/**
|
||||
* Contains the current geo hash (or false if the hash
|
||||
* is not set/cannot be determined).
|
||||
*
|
||||
* @type {boolean|string}
|
||||
*/
|
||||
var geo_hash = false;
|
||||
|
||||
var this_page = window.location.toString();
|
||||
/**
|
||||
* Obtains the current geo hash from the `woocommerce_geo_hash` cookie, if set.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function get_geo_hash() {
|
||||
var geo_hash_cookie = Cookies.get( 'woocommerce_geo_hash' );
|
||||
|
||||
if ( 'string' === typeof geo_hash_cookie && geo_hash_cookie.length ) {
|
||||
geo_hash = geo_hash_cookie;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* If we have an active geo hash value but it does not match the `?v=` query var in
|
||||
* current page URL, that indicates that we need to refresh the page.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function needs_refresh() {
|
||||
return geo_hash && ( new URLSearchParams( window.location.search ) ).get( 'v' ) !== geo_hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends (or replaces) the geo hash used for links on the current page.
|
||||
*/
|
||||
var $append_hashes = function() {
|
||||
if ( wc_geolocation_params.hash ) {
|
||||
$( 'a[href^="' + wc_geolocation_params.home_url + '"]:not(a[href*="v="]), a[href^="/"]:not(a[href*="v="])' ).each( function() {
|
||||
var $this = $( this ),
|
||||
href = $this.attr( 'href' ),
|
||||
href_parts = href.split( '#' );
|
||||
if ( ! geo_hash ) {
|
||||
return;
|
||||
}
|
||||
|
||||
href = href_parts[0];
|
||||
$( 'a[href^="' + wc_geolocation_params.home_url + '"]:not(a[href*="v="]), a[href^="/"]:not(a[href*="v="])' ).each( function() {
|
||||
var $this = $( this ),
|
||||
href = $this.attr( 'href' ),
|
||||
href_parts = href.split( '#' );
|
||||
|
||||
if ( href.indexOf( '?' ) > 0 ) {
|
||||
href = href + '&v=' + wc_geolocation_params.hash;
|
||||
} else {
|
||||
href = href + '?v=' + wc_geolocation_params.hash;
|
||||
}
|
||||
href = href_parts[0];
|
||||
|
||||
if ( typeof href_parts[1] !== 'undefined' && href_parts[1] !== null ) {
|
||||
href = href + '#' + href_parts[1];
|
||||
}
|
||||
if ( href.indexOf( '?' ) > 0 ) {
|
||||
href = href + '&v=' + geo_hash;
|
||||
} else {
|
||||
href = href + '?v=' + geo_hash;
|
||||
}
|
||||
|
||||
$this.attr( 'href', href );
|
||||
});
|
||||
if ( typeof href_parts[1] !== 'undefined' && href_parts[1] !== null ) {
|
||||
href = href + '#' + href_parts[1];
|
||||
}
|
||||
|
||||
$this.attr( 'href', href );
|
||||
});
|
||||
};
|
||||
|
||||
var $geolocate_customer = {
|
||||
url: wc_geolocation_params.wc_ajax_url.toString().replace( '%%endpoint%%', 'get_customer_location' ),
|
||||
type: 'GET',
|
||||
success: function( response ) {
|
||||
if ( response.success && response.data.hash && response.data.hash !== geo_hash ) {
|
||||
$geolocation_redirect( response.data.hash );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Once we have a new hash, we redirect so a new version of the current page
|
||||
* (with correct pricing for the current region, etc) is displayed.
|
||||
*
|
||||
* @param {string} hash
|
||||
*/
|
||||
var $geolocation_redirect = function( hash ) {
|
||||
// Updates our (cookie-based) cache of the hash value. Expires in 1 hour.
|
||||
Cookies.set( 'woocommerce_geo_hash', hash, { expires: 1 / 24 } );
|
||||
|
||||
var this_page = window.location.toString();
|
||||
|
||||
if ( this_page.indexOf( '?v=' ) > 0 || this_page.indexOf( '&v=' ) > 0 ) {
|
||||
this_page = this_page.replace( /v=[^&]+/, 'v=' + hash );
|
||||
} else if ( this_page.indexOf( '?' ) > 0 ) {
|
||||
|
@ -39,50 +96,50 @@ jQuery( function( $ ) {
|
|||
window.location = this_page;
|
||||
};
|
||||
|
||||
var $geolocate_customer = {
|
||||
url: wc_geolocation_params.wc_ajax_url.toString().replace( '%%endpoint%%', 'get_customer_location' ),
|
||||
type: 'GET',
|
||||
success: function( response ) {
|
||||
if ( response.success && response.data.hash && response.data.hash !== wc_geolocation_params.hash ) {
|
||||
$geolocation_redirect( response.data.hash );
|
||||
}
|
||||
/**
|
||||
* Updates any forms on the page so they use the current geo hash.
|
||||
*/
|
||||
function update_forms() {
|
||||
if ( ! geo_hash ) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if ( '1' === wc_geolocation_params.is_available ) {
|
||||
$.ajax( $geolocate_customer );
|
||||
|
||||
// Support form elements
|
||||
$( 'form' ).each( function() {
|
||||
var $this = $( this );
|
||||
$( 'form' ).each( function () {
|
||||
var $this = $( this );
|
||||
var method = $this.attr( 'method' );
|
||||
var hasField = $this.find('input[name="v"]').length > 0;
|
||||
var hasField = $this.find( 'input[name="v"]' ).length > 0;
|
||||
|
||||
if ( method && 'get' === method.toLowerCase() && !hasField ) {
|
||||
$this.append( '<input type="hidden" name="v" value="' + wc_geolocation_params.hash + '" />' );
|
||||
if ( method && 'get' === method.toLowerCase() && ! hasField ) {
|
||||
$this.append( '<input type="hidden" name="v" value="' + geo_hash + '" />' );
|
||||
} else {
|
||||
var href = $this.attr( 'action' );
|
||||
if ( href ) {
|
||||
if ( href.indexOf( '?' ) > 0 ) {
|
||||
$this.attr( 'action', href + '&v=' + wc_geolocation_params.hash );
|
||||
$this.attr( 'action', href + '&v=' + geo_hash );
|
||||
} else {
|
||||
$this.attr( 'action', href + '?v=' + wc_geolocation_params.hash );
|
||||
$this.attr( 'action', href + '?v=' + geo_hash );
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Append hashes on load
|
||||
$append_hashes();
|
||||
}
|
||||
|
||||
// Get the current geo hash. If it doesn't exist, or if it doesn't match the current
|
||||
// page URL, perform a geolocation request.
|
||||
if ( ! get_geo_hash() || needs_refresh() ) {
|
||||
$.ajax( $geolocate_customer );
|
||||
}
|
||||
|
||||
// Page updates.
|
||||
update_forms();
|
||||
$append_hashes();
|
||||
|
||||
$( document.body ).on( 'added_to_cart', function() {
|
||||
$append_hashes();
|
||||
});
|
||||
|
||||
|
||||
// Enable user to trigger manual append hashes on AJAX operations
|
||||
$( document.body ).on( 'woocommerce_append_geo_hashes', function() {
|
||||
$append_hashes();
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
#!/bin/sh
|
||||
|
||||
PROTECTED_BRANCH="master"
|
||||
PROTECTED_BRANCH="trunk"
|
||||
REMOTE_REF=$(echo "$HUSKY_GIT_STDIN" | cut -d " " -f 3)
|
||||
|
||||
if [ -n "$REMOTE_REF" ]; then
|
||||
if [ "refs/heads/${PROTECTED_BRANCH}" = "$REMOTE_REF" ]; then
|
||||
if [ "$TERM" = "dumb" ]; then
|
||||
>&2 echo "Sorry, you are unable to push to master using a GUI client! Please use git CLI."
|
||||
>&2 echo "Sorry, you are unable to push to trunk using a GUI client! Please use git CLI."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf "%sYou're about to push to master, is that what you intended? [y/N]: %s" "$(tput setaf 3)" "$(tput sgr0)"
|
||||
printf "%sYou're about to push to trunk, is that what you intended? [y/N]: %s" "$(tput setaf 3)" "$(tput sgr0)"
|
||||
read -r PROCEED < /dev/tty
|
||||
echo
|
||||
|
||||
if [ "$(echo "${PROCEED:-n}" | tr "[:upper:]" "[:lower:]")" = "y" ]; then
|
||||
echo "$(tput setaf 2)Brace yourself! Pushing to the master branch...$(tput sgr0)"
|
||||
echo "$(tput setaf 2)Brace yourself! Pushing to the trunk branch...$(tput sgr0)"
|
||||
echo
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "$(tput setaf 2)Push to master cancelled!$(tput sgr0)"
|
||||
echo "$(tput setaf 2)Push to trunk cancelled!$(tput sgr0)"
|
||||
echo
|
||||
exit 1
|
||||
fi
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
"pelago/emogrifier": "3.1.0",
|
||||
"psr/container": "1.0.0",
|
||||
"woocommerce/action-scheduler": "3.1.6",
|
||||
"woocommerce/woocommerce-admin": "2.0.1",
|
||||
"woocommerce/woocommerce-admin": "2.0.2",
|
||||
"woocommerce/woocommerce-blocks": "4.4.3"
|
||||
},
|
||||
"require-dev": {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "95f29b23c1baa50f079597378d1673f1",
|
||||
"content-hash": "f24a600ea103061d766dd7b06c13e8f2",
|
||||
"packages": [
|
||||
{
|
||||
"name": "automattic/jetpack-autoloader",
|
||||
|
@ -44,6 +44,9 @@
|
|||
"GPL-2.0-or-later"
|
||||
],
|
||||
"description": "Creates a custom autoloader for a plugin or theme.",
|
||||
"support": {
|
||||
"source": "https://github.com/Automattic/jetpack-autoloader/tree/v2.9.1"
|
||||
},
|
||||
"time": "2021-02-05T19:07:06+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -75,6 +78,9 @@
|
|||
"GPL-2.0-or-later"
|
||||
],
|
||||
"description": "A wrapper for defining constants in a more testable way.",
|
||||
"support": {
|
||||
"source": "https://github.com/Automattic/jetpack-constants/tree/v1.5.1"
|
||||
},
|
||||
"time": "2020-10-28T19:00:31+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -205,6 +211,10 @@
|
|||
"zend",
|
||||
"zikula"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/composer/installers/issues",
|
||||
"source": "https://github.com/composer/installers/tree/v1.10.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://packagist.com",
|
||||
|
@ -279,6 +289,10 @@
|
|||
"geolocation",
|
||||
"maxmind"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/maxmind/MaxMind-DB-Reader-php/issues",
|
||||
"source": "https://github.com/maxmind/MaxMind-DB-Reader-php/tree/v1.6.0"
|
||||
},
|
||||
"time": "2019-12-19T22:59:03+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -353,6 +367,10 @@
|
|||
"email",
|
||||
"pre-processing"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/MyIntervals/emogrifier/issues",
|
||||
"source": "https://github.com/MyIntervals/emogrifier"
|
||||
},
|
||||
"time": "2019-12-26T19:37:31+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -402,6 +420,10 @@
|
|||
"container-interop",
|
||||
"psr"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/php-fig/container/issues",
|
||||
"source": "https://github.com/php-fig/container/tree/master"
|
||||
},
|
||||
"time": "2017-02-14T16:28:37+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -493,20 +515,24 @@
|
|||
],
|
||||
"description": "Action Scheduler for WordPress and WooCommerce",
|
||||
"homepage": "https://actionscheduler.org/",
|
||||
"support": {
|
||||
"issues": "https://github.com/woocommerce/action-scheduler/issues",
|
||||
"source": "https://github.com/woocommerce/action-scheduler/tree/master"
|
||||
},
|
||||
"time": "2020-05-12T16:22:33+00:00"
|
||||
},
|
||||
{
|
||||
"name": "woocommerce/woocommerce-admin",
|
||||
"version": "2.0.1",
|
||||
"version": "2.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/woocommerce/woocommerce-admin.git",
|
||||
"reference": "b7e89c48479348847fc97ae8be8c27d068106d04"
|
||||
"reference": "c4ffd90ebc72652f9d1bc8943a56d7723acc5bf4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/b7e89c48479348847fc97ae8be8c27d068106d04",
|
||||
"reference": "b7e89c48479348847fc97ae8be8c27d068106d04",
|
||||
"url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/c4ffd90ebc72652f9d1bc8943a56d7723acc5bf4",
|
||||
"reference": "c4ffd90ebc72652f9d1bc8943a56d7723acc5bf4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -538,7 +564,11 @@
|
|||
],
|
||||
"description": "A modern, javascript-driven WooCommerce Admin experience.",
|
||||
"homepage": "https://github.com/woocommerce/woocommerce-admin",
|
||||
"time": "2021-02-12T01:19:48+00:00"
|
||||
"support": {
|
||||
"issues": "https://github.com/woocommerce/woocommerce-admin/issues",
|
||||
"source": "https://github.com/woocommerce/woocommerce-admin/tree/v2.0.2"
|
||||
},
|
||||
"time": "2021-02-25T07:29:24+00:00"
|
||||
},
|
||||
{
|
||||
"name": "woocommerce/woocommerce-blocks",
|
||||
|
@ -585,6 +615,10 @@
|
|||
"gutenberg",
|
||||
"woocommerce"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues",
|
||||
"source": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/tree/v4.4.3"
|
||||
},
|
||||
"time": "2021-02-11T18:07:48+00:00"
|
||||
}
|
||||
],
|
||||
|
@ -633,6 +667,10 @@
|
|||
"isolation",
|
||||
"tool"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/bamarni/composer-bin-plugin/issues",
|
||||
"source": "https://github.com/bamarni/composer-bin-plugin/tree/master"
|
||||
},
|
||||
"time": "2020-05-03T08:27:20+00:00"
|
||||
}
|
||||
],
|
||||
|
@ -648,5 +686,5 @@
|
|||
"platform-overrides": {
|
||||
"php": "7.0"
|
||||
},
|
||||
"plugin-api-version": "1.1.0"
|
||||
"plugin-api-version": "2.0.0"
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
# WooCommerce `includes` files
|
||||
|
||||
This directory contains WooCommerce legacy code. Ideally, the code in this folder should only get the minimum required changes for bug fixing, and any new code should go in [the `src` directory](https://github.com/woocommerce/woocommerce/tree/master/src/README.md) instead.
|
||||
This directory contains WooCommerce legacy code. Ideally, the code in this folder should only get the minimum required changes for bug fixing, and any new code should go in [the `src` directory](https://github.com/woocommerce/woocommerce/tree/trunk/src/README.md) instead.
|
||||
|
||||
|
||||
## Interacting with the `src` folder
|
||||
|
||||
Whenever you need to get an instance of a class from the `src` directory, please don't instantiate it directly, but instead use [the container](https://github.com/woocommerce/woocommerce/tree/master/src/README.md#the-container). To get an instance of the container itself you can use the `wc_get_container` function, for example:
|
||||
Whenever you need to get an instance of a class from the `src` directory, please don't instantiate it directly, but instead use [the container](https://github.com/woocommerce/woocommerce/tree/trunk/src/README.md#the-container). To get an instance of the container itself you can use the `wc_get_container` function, for example:
|
||||
|
||||
```php
|
||||
$container = wc_get_container();
|
||||
|
@ -17,18 +17,18 @@ The exception to this rule might be data-only classes that could be created the
|
|||
|
||||
## Adding new actions and filters
|
||||
|
||||
Please take a look at [the considerations for creation new hooks in `src` code](https://github.com/woocommerce/woocommerce/tree/master/src/README.md#defining-new-actions-and-filters), as they apply for `includes` code as well. The short version is that **new hooks should be introduced only if they provide a valuable extension point for plugins**, and not with the purpose of driving WooCommerce's internal logic.
|
||||
Please take a look at [the considerations for creation new hooks in `src` code](https://github.com/woocommerce/woocommerce/tree/trunk/src/README.md#defining-new-actions-and-filters), as they apply for `includes` code as well. The short version is that **new hooks should be introduced only if they provide a valuable extension point for plugins**, and not with the purpose of driving WooCommerce's internal logic.
|
||||
|
||||
|
||||
## Writing unit tests
|
||||
|
||||
[As it's the case for the `src` folder](https://github.com/woocommerce/woocommerce/tree/master/src/README.md#writing-unit-tests), writing unit tests is generally mandatory if you are a WooCommerce team member or a contributor from another Automattic team, and encouraged if you are an external contributor. Tests should cover any new code (although as mentioned, adding new code in `includes` should be rare) and any modifications to existing code.
|
||||
[As it's the case for the `src` folder](https://github.com/woocommerce/woocommerce/tree/trunk/src/README.md#writing-unit-tests), writing unit tests is generally mandatory if you are a WooCommerce team member or a contributor from another Automattic team, and encouraged if you are an external contributor. Tests should cover any new code (although as mentioned, adding new code in `includes` should be rare) and any modifications to existing code.
|
||||
|
||||
In order to make it easier to write unit tests, there are a couple of mechanisms in place that you can use:
|
||||
|
||||
* [The code hacker](https://github.com/woocommerce/woocommerce/blob/master/tests/Tools/CodeHacking/README.md). Pros: you don't need to do any special changes to your code to make it testable. Cons: it's a hack, the tested code is being actually modified while being loaded by the PHP engine, so not an ideal solution.
|
||||
* [The code hacker](https://github.com/woocommerce/woocommerce/blob/trunk/tests/Tools/CodeHacking/README.md). Pros: you don't need to do any special changes to your code to make it testable. Cons: it's a hack, the tested code is being actually modified while being loaded by the PHP engine, so not an ideal solution.
|
||||
|
||||
* [The legacy proxy and the related helper methods in WC_Unit_Test_case](https://github.com/woocommerce/woocommerce/tree/master/src/README.md#interacting-with-legacy-code): although these are intended in principle for writing tests for code in the `src` directory, they can be used for `includes` code as well. Pros: a clean approach, no hacks involved. Cons: you need to modify your code to use the proxy whenever you need to call a function or static method that makes the code difficult to test.
|
||||
* [The legacy proxy and the related helper methods in WC_Unit_Test_case](https://github.com/woocommerce/woocommerce/tree/trunk/src/README.md#interacting-with-legacy-code): although these are intended in principle for writing tests for code in the `src` directory, they can be used for `includes` code as well. Pros: a clean approach, no hacks involved. Cons: you need to modify your code to use the proxy whenever you need to call a function or static method that makes the code difficult to test.
|
||||
|
||||
It's up to you as a contributor to decide which mechanism to use in each case. Choose wisely.
|
||||
|
||||
|
|
|
@ -641,7 +641,7 @@ class WC_Admin_Addons {
|
|||
self::install_woocommerce_services_addon();
|
||||
break;
|
||||
case 'woocommerce-payments':
|
||||
self::install_woocommerce_payments_addon();
|
||||
self::install_woocommerce_payments_addon( $section );
|
||||
break;
|
||||
default:
|
||||
// Do nothing.
|
||||
|
@ -693,9 +693,11 @@ class WC_Admin_Addons {
|
|||
/**
|
||||
* Install WooCommerce Payments from the Extensions screens.
|
||||
*
|
||||
* @param string $section Optional. Extenstions tab.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function install_woocommerce_payments_addon() {
|
||||
public static function install_woocommerce_payments_addon( $section = '_featured' ) {
|
||||
check_admin_referer( 'install-addon_woocommerce-payments' );
|
||||
|
||||
$wcpay_plugin_id = 'woocommerce-payments';
|
||||
|
@ -704,7 +706,9 @@ class WC_Admin_Addons {
|
|||
'repo-slug' => 'woocommerce-payments',
|
||||
);
|
||||
|
||||
WC_Install::background_installer( $services_plugin_id, $wcpay_plugin );
|
||||
WC_Install::background_installer( $wcpay_plugin_id, $wcpay_plugin );
|
||||
|
||||
do_action( 'woocommerce_addon_installed', $wcpay_plugin_id, $section );
|
||||
|
||||
wp_safe_redirect( remove_query_arg( array( 'install-addon', '_wpnonce' ) ) );
|
||||
exit;
|
||||
|
|
|
@ -116,15 +116,23 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) :
|
|||
|
||||
$reports = new WC_Admin_Report();
|
||||
|
||||
$net_sales_link = 'admin.php?page=wc-reports&tab=orders&range=month';
|
||||
$top_seller_link = 'admin.php?page=wc-reports&tab=orders&report=sales_by_product&range=month&product_ids=';
|
||||
$report_data = $is_wc_admin_disabled ? $this->get_sales_report_data() : $this->get_wc_admin_performance_data();
|
||||
if ( ! $is_wc_admin_disabled ) {
|
||||
$net_sales_link = 'admin.php?page=wc-admin&path=%2Fanalytics%2Frevenue&chart=net_revenue&orderby=net_revenue&period=month&compare=previous_period';
|
||||
$top_seller_link = 'admin.php?page=wc-admin&filter=single_product&path=%2Fanalytics%2Fproducts&products=';
|
||||
}
|
||||
|
||||
echo '<ul class="wc_status_list">';
|
||||
|
||||
if ( current_user_can( 'view_woocommerce_reports' ) ) {
|
||||
$report_data = $this->get_sales_report_data();
|
||||
|
||||
if ( $report_data ) {
|
||||
?>
|
||||
<li class="sales-this-month">
|
||||
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wc-reports&tab=orders&range=month' ) ); ?>">
|
||||
<?php echo $reports->sales_sparkline( '', max( 7, gmdate( 'd', current_time( 'timestamp' ) ) ) ); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped ?>
|
||||
<a href="<?php echo esc_url( admin_url( $net_sales_link ) ); ?>">
|
||||
<?php echo $this->sales_sparkline( $reports, $is_wc_admin_disabled, '' ); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped ?>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s: net sales */
|
||||
|
@ -141,8 +149,8 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) :
|
|||
if ( $top_seller && $top_seller->qty ) {
|
||||
?>
|
||||
<li class="best-seller-this-month">
|
||||
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wc-reports&tab=orders&report=sales_by_product&range=month&product_ids=' . $top_seller->product_id ) ); ?>">
|
||||
<?php echo $reports->sales_sparkline( $top_seller->product_id, max( 7, gmdate( 'd', current_time( 'timestamp' ) ) ), 'count' ); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped ?>
|
||||
<a href="<?php echo esc_url( admin_url( $top_seller_link . $top_seller->product_id ) ); ?>">
|
||||
<?php echo $this->sales_sparkline( $reports, $is_wc_admin_disabled, $top_seller->product_id, 'count' ); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped ?>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: 1: top seller product title 2: top seller quantity */
|
||||
|
@ -432,6 +440,106 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) :
|
|||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the sales performance data from the new WooAdmin store.
|
||||
*
|
||||
* @return stdClass|WP_Error|WP_REST_Response
|
||||
*/
|
||||
private function get_wc_admin_performance_data() {
|
||||
$request = new \WP_REST_Request( 'GET', '/wc-analytics/reports/performance-indicators' );
|
||||
$start_date = gmdate( 'Y-m-01 00:00:00', current_time( 'timestamp' ) );
|
||||
$end_date = gmdate( 'Y-m-d 23:59:59', current_time( 'timestamp' ) );
|
||||
$request->set_query_params(
|
||||
array(
|
||||
'before' => $end_date,
|
||||
'after' => $start_date,
|
||||
'stats' => 'revenue/total_sales,revenue/net_revenue,orders/orders_count,products/items_sold,variations/items_sold',
|
||||
)
|
||||
);
|
||||
$response = rest_do_request( $request );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
if ( 200 !== $response->get_status() ) {
|
||||
return new \WP_Error( 'woocommerce_analytics_performance_indicators_result_failed', __( 'Sorry, fetching performance indicators failed.', 'woocommerce' ) );
|
||||
}
|
||||
$report_keys = array(
|
||||
'net_revenue' => 'net_sales',
|
||||
);
|
||||
$performance_data = new stdClass();
|
||||
foreach ( $response->get_data() as $indicator ) {
|
||||
if ( isset( $indicator['chart'] ) && isset( $indicator['value'] ) ) {
|
||||
$key = isset( $report_keys[ $indicator['chart'] ] ) ? $report_keys[ $indicator['chart'] ] : $indicator['chart'];
|
||||
$performance_data->$key = $indicator['value'];
|
||||
}
|
||||
}
|
||||
return $performance_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrites the original sparkline to use the new reports data if WooAdmin is enabled.
|
||||
* Prepares a sparkline to show sales in the last X days.
|
||||
*
|
||||
* @param WC_Admin_Report $reports old class for getting reports.
|
||||
* @param bool $is_wc_admin_disabled If WC Admin is disabled or not.
|
||||
* @param int $id ID of the product to show. Blank to get all orders.
|
||||
* @param string $type Type of sparkline to get. Ignored if ID is not set.
|
||||
* @return string
|
||||
*/
|
||||
private function sales_sparkline( $reports, $is_wc_admin_disabled = false, $id = '', $type = 'sales' ) {
|
||||
$days = max( 7, gmdate( 'd', current_time( 'timestamp' ) ) );
|
||||
if ( $is_wc_admin_disabled ) {
|
||||
return $reports->sales_sparkline( $id, $days, $type );
|
||||
}
|
||||
$sales_endpoint = '/wc-analytics/reports/revenue/stats';
|
||||
$start_date = gmdate( 'Y-m-d 00:00:00', current_time( 'timestamp' ) - ( ( $days - 1 ) * DAY_IN_SECONDS ) );
|
||||
$end_date = gmdate( 'Y-m-d 23:59:59', current_time( 'timestamp' ) );
|
||||
$meta_key = 'net_revenue';
|
||||
$params = array(
|
||||
'order' => 'asc',
|
||||
'interval' => 'day',
|
||||
'per_page' => 100,
|
||||
'before' => $end_date,
|
||||
'after' => $start_date,
|
||||
);
|
||||
if ( $id ) {
|
||||
$sales_endpoint = '/wc-analytics/reports/products/stats';
|
||||
$meta_key = ( 'sales' === $type ) ? 'net_revenue' : 'items_sold';
|
||||
$params['products'] = $id;
|
||||
}
|
||||
$request = new \WP_REST_Request( 'GET', $sales_endpoint );
|
||||
$params['fields'] = array( $meta_key );
|
||||
$request->set_query_params( $params );
|
||||
|
||||
$response = rest_do_request( $request );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$resp_data = $response->get_data();
|
||||
$data = $resp_data['intervals'];
|
||||
|
||||
$sparkline_data = array();
|
||||
$total = 0;
|
||||
foreach ( $data as $d ) {
|
||||
$total += $d['subtotals']->$meta_key;
|
||||
array_push( $sparkline_data, array( strval( strtotime( $d['interval'] ) * 1000 ), $d['subtotals']->$meta_key ) );
|
||||
}
|
||||
|
||||
if ( 'sales' === $type ) {
|
||||
/* translators: 1: total income 2: days */
|
||||
$tooltip = sprintf( __( 'Sold %1$s worth in the last %2$d days', 'woocommerce' ), strip_tags( wc_price( $total ) ), $days );
|
||||
} else {
|
||||
/* translators: 1: total items sold 2: days */
|
||||
$tooltip = sprintf( _n( 'Sold %1$d item in the last %2$d days', 'Sold %1$d items in the last %2$d days', $total, 'woocommerce' ), $total, $days );
|
||||
}
|
||||
|
||||
return '<span class="wc_sparkline ' . ( ( 'sales' === $type ) ? 'lines' : 'bars' ) . ' tips" data-color="#777" data-tip="' . esc_attr( $tooltip ) . '" data-barwidth="' . 60 * 60 * 16 * 1000 . '" data-sparkline="' . wc_esc_json( wp_json_encode( $sparkline_data ) ) . '"></span>';
|
||||
}
|
||||
}
|
||||
|
||||
endif;
|
||||
|
|
|
@ -65,7 +65,7 @@ class WC_Admin_Help {
|
|||
'content' =>
|
||||
'<h2>' . __( 'Found a bug?', 'woocommerce' ) . '</h2>' .
|
||||
/* translators: 1: GitHub issues URL 2: GitHub contribution guide URL 3: System status report URL */
|
||||
'<p>' . sprintf( __( 'If you find a bug within WooCommerce core you can create a ticket via <a href="%1$s">Github issues</a>. Ensure you read the <a href="%2$s">contribution guide</a> prior to submitting your report. To help us solve your issue, please be as descriptive as possible and include your <a href="%3$s">system status report</a>.', 'woocommerce' ), 'https://github.com/woocommerce/woocommerce/issues?state=open', 'https://github.com/woocommerce/woocommerce/blob/master/.github/CONTRIBUTING.md', admin_url( 'admin.php?page=wc-status' ) ) . '</p>' .
|
||||
'<p>' . sprintf( __( 'If you find a bug within WooCommerce core you can create a ticket via <a href="%1$s">Github issues</a>. Ensure you read the <a href="%2$s">contribution guide</a> prior to submitting your report. To help us solve your issue, please be as descriptive as possible and include your <a href="%3$s">system status report</a>.', 'woocommerce' ), 'https://github.com/woocommerce/woocommerce/issues?state=open', 'https://github.com/woocommerce/woocommerce/blob/trunk/.github/CONTRIBUTING.md', admin_url( 'admin.php?page=wc-status' ) ) . '</p>' .
|
||||
'<p><a href="https://github.com/woocommerce/woocommerce/issues/new?template=4-Bug-report.md" class="button button-primary">' . __( 'Report a bug', 'woocommerce' ) . '</a> <a href="' . admin_url( 'admin.php?page=wc-status' ) . '" class="button">' . __( 'System status', 'woocommerce' ) . '</a></p>',
|
||||
|
||||
)
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
* @version 3.2.0
|
||||
*/
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
@ -37,8 +39,13 @@ class WC_Updates_Screen_Updates extends WC_Plugin_Updates {
|
|||
return;
|
||||
}
|
||||
|
||||
$version_type = Constants::get_constant( 'WC_SSR_PLUGIN_UPDATE_RELEASE_VERSION_TYPE' );
|
||||
if ( ! is_string( $version_type ) ) {
|
||||
$version_type = 'none';
|
||||
}
|
||||
|
||||
$this->new_version = wc_clean( $updateable_plugins['woocommerce/woocommerce.php']->update->new_version );
|
||||
$this->major_untested_plugins = $this->get_untested_plugins( $this->new_version, 'major' );
|
||||
$this->major_untested_plugins = $this->get_untested_plugins( $this->new_version, $version_type );
|
||||
|
||||
if ( ! empty( $this->major_untested_plugins ) ) {
|
||||
echo $this->get_extensions_modal_warning(); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
|
||||
|
|
|
@ -64,10 +64,6 @@ class WC_Settings_Products extends WC_Settings_Page {
|
|||
$settings = $this->get_settings( $current_section );
|
||||
WC_Admin_Settings::save_fields( $settings );
|
||||
|
||||
// Any time we update the product settings, we should flush the term count cache.
|
||||
$tools_controller = new WC_REST_System_Status_Tools_Controller();
|
||||
$tools_controller->execute_tool( 'recount_terms' );
|
||||
|
||||
if ( $current_section ) {
|
||||
do_action( 'woocommerce_update_options_' . $this->id . '_' . $current_section );
|
||||
}
|
||||
|
|
|
@ -5,16 +5,12 @@
|
|||
* @package WooCommerce
|
||||
*/
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
global $wpdb;
|
||||
|
||||
if ( ! defined( 'WC_SSR_PLUGIN_UPDATE_RELEASE_VERSION_TYPE' ) ) {
|
||||
// Define if we're checking against major or minor versions.
|
||||
// Since 5.0 all versions are backwards compatible, so there's no more check.
|
||||
define( 'WC_SSR_PLUGIN_UPDATE_RELEASE_VERSION_TYPE', 'none' );
|
||||
}
|
||||
|
||||
$report = wc()->api->get_endpoint_data( '/wc/v3/system_status' );
|
||||
$environment = $report['environment'];
|
||||
$database = $report['database'];
|
||||
|
@ -27,7 +23,7 @@ $security = $report['security'];
|
|||
$settings = $report['settings'];
|
||||
$wp_pages = $report['pages'];
|
||||
$plugin_updates = new WC_Plugin_Updates();
|
||||
$untested_plugins = $plugin_updates->get_untested_plugins( WC()->version, WC_SSR_PLUGIN_UPDATE_RELEASE_VERSION_TYPE );
|
||||
$untested_plugins = $plugin_updates->get_untested_plugins( WC()->version, Constants::get_constant( 'WC_SSR_PLUGIN_UPDATE_RELEASE_VERSION_TYPE' ) );
|
||||
?>
|
||||
<div class="updated woocommerce-message inline">
|
||||
<p>
|
||||
|
|
|
@ -1273,7 +1273,6 @@ class WC_AJAX {
|
|||
}
|
||||
|
||||
if ( ! empty( $order_item_ids ) ) {
|
||||
$order_notes = array();
|
||||
|
||||
foreach ( $order_item_ids as $item_id ) {
|
||||
$item_id = absint( $item_id );
|
||||
|
@ -1300,6 +1299,18 @@ class WC_AJAX {
|
|||
$order->calculate_taxes( $calculate_tax_args );
|
||||
$order->calculate_totals( false );
|
||||
|
||||
/**
|
||||
* Fires after order items are removed.
|
||||
*
|
||||
* @since 5.2.0
|
||||
*
|
||||
* @param int $item_id WC item ID.
|
||||
* @param WC_Order_Item|false $item As returned by $order->get_item( $item_id ).
|
||||
* @param bool|array|WP_Error $changed_store Result of wc_maybe_adjust_line_item_product_stock().
|
||||
* @param bool|WC_Order|WC_Order_Refund $order As returned by wc_get_order().
|
||||
*/
|
||||
do_action( 'woocommerce_ajax_order_items_removed', $item_id, $item, $changed_stock, $order );
|
||||
|
||||
// Get HTML to return.
|
||||
ob_start();
|
||||
include __DIR__ . '/admin/meta-boxes/views/html-order-items.php';
|
||||
|
|
|
@ -26,6 +26,7 @@ class WC_Cache_Helper {
|
|||
add_filter( 'nocache_headers', array( __CLASS__, 'additional_nocache_headers' ), 10 );
|
||||
add_action( 'shutdown', array( __CLASS__, 'delete_transients_on_shutdown' ), 10 );
|
||||
add_action( 'template_redirect', array( __CLASS__, 'geolocation_ajax_redirect' ) );
|
||||
add_action( 'wc_ajax_update_order_review', array( __CLASS__, 'update_geolocation_hash' ), 5 );
|
||||
add_action( 'admin_notices', array( __CLASS__, 'notices' ) );
|
||||
add_action( 'delete_version_transients', array( __CLASS__, 'delete_version_transients' ), 10 );
|
||||
add_action( 'wp', array( __CLASS__, 'prevent_caching' ) );
|
||||
|
@ -190,6 +191,24 @@ class WC_Cache_Helper {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the `woocommerce_geo_hash` cookie, which is used to help ensure we display
|
||||
* the correct pricing etc to customers, according to their billing country.
|
||||
*
|
||||
* Note that:
|
||||
*
|
||||
* A) This only sets the cookie if the default customer address is set to "Geolocate (with
|
||||
* Page Caching Support)".
|
||||
*
|
||||
* B) It is hooked into the `wc_ajax_update_order_review` action, which has the benefit of
|
||||
* ensuring we update the cookie any time the billing country is changed.
|
||||
*/
|
||||
public static function update_geolocation_hash() {
|
||||
if ( 'geolocation_ajax' === get_option( 'woocommerce_default_customer_address' ) ) {
|
||||
wc_setcookie( 'woocommerce_geo_hash', static::geolocation_ajax_get_location_hash(), time() + HOUR_IN_SECONDS );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get transient version.
|
||||
*
|
||||
|
|
|
@ -1120,6 +1120,20 @@ class WC_Cart extends WC_Legacy_Cart {
|
|||
}
|
||||
}
|
||||
|
||||
// Validate variation ID.
|
||||
if (
|
||||
0 < $variation_id && // Only check if there's any variation_id.
|
||||
(
|
||||
! $product_data->is_type( 'variation' ) || // Check if isn't a variation, it suppose to be a variation at this point.
|
||||
$product_data->get_parent_id() !== $product_id // Check if belongs to the selected variable product.
|
||||
)
|
||||
) {
|
||||
$product = wc_get_product( $product_id );
|
||||
|
||||
/* translators: 1: product link, 2: product name */
|
||||
throw new Exception( sprintf( __( 'The selected product isn\'t a variation of %2$s, please choose product options by visiting <a href="%1$s" title="%2$s">%2$s</a>.', 'woocommerce' ), esc_url( $product->get_permalink() ), esc_html( $product->get_name() ) ) );
|
||||
}
|
||||
|
||||
// Load cart item data - may be added by other plugins.
|
||||
$cart_item_data = (array) apply_filters( 'woocommerce_add_cart_item_data', $cart_item_data, $product_id, $variation_id, $quantity );
|
||||
|
||||
|
|
|
@ -398,7 +398,12 @@ class WC_Frontend_Scripts {
|
|||
self::enqueue_script( 'wc-single-product' );
|
||||
}
|
||||
|
||||
if ( 'geolocation_ajax' === get_option( 'woocommerce_default_customer_address' ) ) {
|
||||
// Only enqueue the geolocation script if the Default Current Address is set to "Geolocate
|
||||
// (with Page Caching Support) and outside of the cart, checkout, account and customizer preview.
|
||||
if (
|
||||
'geolocation_ajax' === get_option( 'woocommerce_default_customer_address' )
|
||||
&& ! ( is_cart() || is_account_page() || is_checkout() || is_customize_preview() )
|
||||
) {
|
||||
$ua = strtolower( wc_get_user_agent() ); // Exclude common bots from geolocation by user agent.
|
||||
|
||||
if ( ! strstr( $ua, 'bot' ) && ! strstr( $ua, 'spider' ) && ! strstr( $ua, 'crawl' ) ) {
|
||||
|
@ -473,8 +478,6 @@ class WC_Frontend_Scripts {
|
|||
$params = array(
|
||||
'wc_ajax_url' => WC_AJAX::get_endpoint( '%%endpoint%%' ),
|
||||
'home_url' => remove_query_arg( 'lang', home_url() ), // FIX for WPML compatibility.
|
||||
'is_available' => ! ( is_cart() || is_account_page() || is_checkout() || is_customize_preview() ) ? '1' : '0',
|
||||
'hash' => isset( $_GET['v'] ) ? wc_clean( wp_unslash( $_GET['v'] ) ) : '', // WPCS: input var ok, CSRF ok.
|
||||
);
|
||||
break;
|
||||
case 'wc-single-product':
|
||||
|
|
|
@ -166,6 +166,9 @@ class WC_Tracker {
|
|||
// Cart & checkout tech (blocks or shortcodes).
|
||||
$data['cart_checkout'] = self::get_cart_checkout_info();
|
||||
|
||||
// WooCommerce Admin info.
|
||||
$data['wc_admin_disabled'] = apply_filters( 'woocommerce_admin_disabled', false ) ? 'yes' : 'no';
|
||||
|
||||
return apply_filters( 'woocommerce_tracker_data', $data );
|
||||
}
|
||||
|
||||
|
|
|
@ -249,6 +249,18 @@ final class WooCommerce {
|
|||
$this->define( 'WC_NOTICE_MIN_PHP_VERSION', '7.2' );
|
||||
$this->define( 'WC_NOTICE_MIN_WP_VERSION', '5.2' );
|
||||
$this->define( 'WC_PHP_MIN_REQUIREMENTS_NOTICE', 'wp_php_min_requirements_' . WC_NOTICE_MIN_PHP_VERSION . '_' . WC_NOTICE_MIN_WP_VERSION );
|
||||
/** Define if we're checking against major, minor or no versions in the following places:
|
||||
* - plugin screen in WP Admin (displaying extra warning when updating to new major versions)
|
||||
* - System Status Report ('Installed version not tested with active version of WooCommerce' warning)
|
||||
* - core update screen in WP Admin (displaying extra warning when updating to new major versions)
|
||||
* - enable/disable automated updates in the plugin screen in WP Admin (if there are any plugins
|
||||
* that don't declare compatibility, the auto-update is disabled)
|
||||
*
|
||||
* We dropped SemVer before WC 5.0, so all versions are backwards compatible now, thus no more check needed.
|
||||
* The SSR in the name is preserved for bw compatibility, as this was initially used in System Status Report.
|
||||
*/
|
||||
$this->define( 'WC_SSR_PLUGIN_UPDATE_RELEASE_VERSION_TYPE', 'none' );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -272,9 +272,6 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
|
|||
|
||||
$product->apply_changes();
|
||||
|
||||
// Any time we update the product, we should flush the term count cache.
|
||||
$tools_controller = new WC_REST_System_Status_Tools_Controller();
|
||||
$tools_controller->execute_tool( 'recount_terms' );
|
||||
do_action( 'woocommerce_update_product', $product->get_id(), $product );
|
||||
}
|
||||
|
||||
|
|
|
@ -24,9 +24,6 @@ class WC_Twenty_Twenty_One {
|
|||
remove_action( 'woocommerce_before_main_content', 'woocommerce_output_content_wrapper', 10 );
|
||||
remove_action( 'woocommerce_after_main_content', 'woocommerce_output_content_wrapper_end', 10 );
|
||||
|
||||
add_action( 'woocommerce_before_main_content', array( __CLASS__, 'output_content_wrapper' ), 10 );
|
||||
add_action( 'woocommerce_after_main_content', array( __CLASS__, 'output_content_wrapper_end' ), 10 );
|
||||
|
||||
// This theme doesn't have a traditional sidebar.
|
||||
remove_action( 'woocommerce_sidebar', 'woocommerce_get_sidebar', 10 );
|
||||
|
||||
|
@ -34,7 +31,7 @@ class WC_Twenty_Twenty_One {
|
|||
add_filter( 'woocommerce_enqueue_styles', array( __CLASS__, 'enqueue_styles' ) );
|
||||
|
||||
// Enqueue wp-admin compatibility styles.
|
||||
add_action( 'admin_enqueue_scripts', array( __CLASS__ , 'enqueue_admin_styles' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_admin_styles' ) );
|
||||
|
||||
// Register theme features.
|
||||
add_theme_support( 'wc-product-gallery-zoom' );
|
||||
|
@ -50,22 +47,6 @@ class WC_Twenty_Twenty_One {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the Twenty Twenty One wrapper.
|
||||
*/
|
||||
public static function output_content_wrapper() {
|
||||
echo '<section id="primary" class="content-area">';
|
||||
echo '<main id="main" class="site-main">';
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the Twenty Twenty One wrapper.
|
||||
*/
|
||||
public static function output_content_wrapper_end() {
|
||||
echo '</main>';
|
||||
echo '</section>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue CSS for this theme.
|
||||
*
|
||||
|
|
|
@ -21,6 +21,7 @@ class WC_Extensions_Tracking {
|
|||
add_action( 'woocommerce_helper_connected', array( $this, 'track_helper_connection_complete' ) );
|
||||
add_action( 'woocommerce_helper_disconnected', array( $this, 'track_helper_disconnected' ) );
|
||||
add_action( 'woocommerce_helper_subscriptions_refresh', array( $this, 'track_helper_subscriptions_refresh' ) );
|
||||
add_action( 'woocommerce_addon_installed', array( $this, 'track_addon_install' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -76,4 +77,21 @@ class WC_Extensions_Tracking {
|
|||
public function track_helper_subscriptions_refresh() {
|
||||
WC_Tracks::record_event( 'extensions_subscriptions_update' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a Tracks event when addon is installed via the Extensions page.
|
||||
*
|
||||
* @param string $addon_id Addon slug.
|
||||
* @param string $section Extensions tab.
|
||||
*/
|
||||
public function track_addon_install( $addon_id, $section ) {
|
||||
$properties = array(
|
||||
'context' => 'extensions',
|
||||
'section' => $section,
|
||||
);
|
||||
|
||||
if ( 'woocommerce-payments' === $addon_id ) {
|
||||
WC_Tracks::record_event( 'woocommerce_payments_install', $properties );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2255,7 +2255,11 @@ function wc_prevent_dangerous_auto_updates( $should_update, $plugin ) {
|
|||
|
||||
$new_version = wc_clean( $plugin->new_version );
|
||||
$plugin_updates = new WC_Plugin_Updates();
|
||||
$untested_plugins = $plugin_updates->get_untested_plugins( $new_version, 'major' );
|
||||
$version_type = Constants::get_constant( 'WC_SSR_PLUGIN_UPDATE_RELEASE_VERSION_TYPE' );
|
||||
if ( ! is_string( $version_type ) ) {
|
||||
$version_type = 'none';
|
||||
}
|
||||
$untested_plugins = $plugin_updates->get_untested_plugins( $new_version, $version_type );
|
||||
if ( ! empty( $untested_plugins ) ) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ Tags: e-commerce, store, sales, sell, woo, shop, cart, checkout, downloadable, d
|
|||
Requires at least: 5.4
|
||||
Tested up to: 5.6
|
||||
Requires PHP: 7.0
|
||||
Stable tag: 4.9.2
|
||||
Stable tag: 5.0.0
|
||||
License: GPLv3
|
||||
License URI: https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
|
@ -160,6 +160,6 @@ WooCommerce comes with some sample data you can use to see how products look; im
|
|||
|
||||
== Changelog ==
|
||||
|
||||
= 5.1.0 2021-03-xx =
|
||||
= 5.2.0 2021-04-xx =
|
||||
|
||||
[See changelog for all versions](https://raw.githubusercontent.com/woocommerce/woocommerce/master/changelog.txt).
|
||||
[See changelog for all versions](https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/changelog.txt).
|
||||
|
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
All the code in this directory (and hence in the `Automattic\WooCommerce\Internal` namespace) is internal WooCommerce infrastructure code and not intended to be used by plugins. The important thing that this implies is that **backwards compatibility of the public surface for classes in this namespace is not guaranteed in future releases of WooCommerce**.
|
||||
|
||||
Therefore **plugin developers should never use classes in this namespace directly in their code**. See [the README file for the src folder](https://github.com/woocommerce/woocommerce/blob/master/src/README.md#the-internal-namespace) for more detailed guidance.
|
||||
Therefore **plugin developers should never use classes in this namespace directly in their code**. See [the README file for the src folder](https://github.com/woocommerce/woocommerce/blob/trunk/src/README.md#the-internal-namespace) for more detailed guidance.
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
This directory is home to new WooCommerce class files under the `Automattic\WooCommerce` namespace using [PSR-4](https://www.php-fig.org/psr/psr-4/) file naming. This is to take full advantage of autoloading.
|
||||
|
||||
Ideally, all the new code for WooCommerce should consist of classes following the PSR-4 naming and living in this directory, and the code in [the `includes` directory](https://github.com/woocommerce/woocommerce/tree/master/includes/README.md) should receive the minimum amount of changes required for bug fixing. This will not always be possible but that should be the rule of thumb.
|
||||
Ideally, all the new code for WooCommerce should consist of classes following the PSR-4 naming and living in this directory, and the code in [the `includes` directory](https://github.com/woocommerce/woocommerce/tree/trunk/includes/README.md) should receive the minimum amount of changes required for bug fixing. This will not always be possible but that should be the rule of thumb.
|
||||
|
||||
A [PSR-11](https://www.php-fig.org/psr/psr-11/) container is in place for registering and resolving the classes in this directory by using the [dependency injection](https://en.wikipedia.org/wiki/Dependency_injection) pattern. There are tools in place to interact with legacy code (and code outside the `src`directory in general) in a way that makes it easy to write unit tests.
|
||||
|
||||
|
@ -281,7 +281,7 @@ What this implies for you as developer depends on what type of contribution are
|
|||
|
||||
Here by "legacy code" we refer mainly to the old WooCommerce code in the `includes` directory, but the mechanisms described in this section are useful for dealing with any code outside the `src` directory.
|
||||
|
||||
The code in the `src` directory can for sure interact directly with legacy code. A function needs to be called? Call it. You need an instance of an object? Instantiate it. The problem is that this makes the code difficult to test: it's not easy to mock functions (unless you use [hacks](https://github.com/woocommerce/woocommerce/blob/master/tests/Tools/CodeHacking/README.md), or objects that are instantiated directly with `new` or whose instance is retrieved via a `TheClass::instance()` method).
|
||||
The code in the `src` directory can for sure interact directly with legacy code. A function needs to be called? Call it. You need an instance of an object? Instantiate it. The problem is that this makes the code difficult to test: it's not easy to mock functions (unless you use [hacks](https://github.com/woocommerce/woocommerce/blob/trunk/tests/Tools/CodeHacking/README.md), or objects that are instantiated directly with `new` or whose instance is retrieved via a `TheClass::instance()` method).
|
||||
|
||||
But we want the WooCommerce code base (and especially the code in `src`) to be well covered by unit tests, and so there are mechanisms in place to interact with legacy code while keeping the code testable.
|
||||
|
||||
|
@ -355,7 +355,7 @@ $this->register_legacy_proxy_function_mocks(
|
|||
|
||||
Of course, for the cases where no mocks are defined `MockableLegacyProxy` works the same way as `LegacyProxy`.
|
||||
|
||||
Please see [the code of the MockableLegacyProxy class](https://github.com/woocommerce/woocommerce/blob/master/tests/Tools/DependencyManagement/MockableLegacyProxy.php) and [its unit tests](https://github.com/woocommerce/woocommerce/blob/master/tests/php/src/Proxies/MockableLegacyProxyTest.php) for more detailed usage instructions and examples.
|
||||
Please see [the code of the MockableLegacyProxy class](https://github.com/woocommerce/woocommerce/blob/trunk/tests/Tools/DependencyManagement/MockableLegacyProxy.php) and [its unit tests](https://github.com/woocommerce/woocommerce/blob/trunk/tests/php/src/Proxies/MockableLegacyProxyTest.php) for more detailed usage instructions and examples.
|
||||
|
||||
### But how does `get_instance_of` work?
|
||||
|
||||
|
@ -363,7 +363,7 @@ We use a container to resolve instances of classes in the `src` directory, but h
|
|||
|
||||
This is a mostly ad-hoc process. When a class has a special way to be instantiated or retrieved (e.g. a static `instance` method), then that is used; otherwise the method fallbacks to simply creating a new instance of the class using `new`.
|
||||
|
||||
This means that the `get_instance_of` method will most likely need to evolve over time to cover additional special cases. Take a look at the method code in [LegacyProxy](https://github.com/woocommerce/woocommerce/blob/master/src/Proxies/LegacyProxy.php) for details on how to properly make changes to the method.
|
||||
This means that the `get_instance_of` method will most likely need to evolve over time to cover additional special cases. Take a look at the method code in [LegacyProxy](https://github.com/woocommerce/woocommerce/blob/trunk/src/Proxies/LegacyProxy.php) for details on how to properly make changes to the method.
|
||||
|
||||
### Creating specialized proxies
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# WooCommerce Tests
|
||||
|
||||
This document discusses unit tests. See [the e2e README](https://github.com/woocommerce/woocommerce/tree/master/tests/e2e) to learn how to setup testing environment for running e2e tests and run them.
|
||||
This document discusses unit tests. See [the e2e README](https://github.com/woocommerce/woocommerce/tree/trunk/tests/e2e) to learn how to setup testing environment for running e2e tests and run them.
|
||||
|
||||
|
||||
## Table of contents
|
||||
|
@ -108,7 +108,7 @@ Each test file should correspond to an associated source file and be named accor
|
|||
* When testing functions: use one test file per functions group file, for example `wc-formatting-functions-test.php` for code in the `wc-formatting-functions.php` file.
|
||||
|
||||
|
||||
See also [the guidelines for writing unit tests for `src` code](https://github.com/woocommerce/woocommerce/tree/master/src/README.md#writing-unit-tests) and [the guidelines for `includes` code](https://github.com/woocommerce/woocommerce/tree/master/includes/README.md#writing-unit-tests).
|
||||
See also [the guidelines for writing unit tests for `src` code](https://github.com/woocommerce/woocommerce/tree/trunk/src/README.md#writing-unit-tests) and [the guidelines for `includes` code](https://github.com/woocommerce/woocommerce/tree/trunk/includes/README.md#writing-unit-tests).
|
||||
|
||||
General guidelines for all the unit tests:
|
||||
|
||||
|
|
|
@ -101,7 +101,7 @@ Puppeteer will still automatically download Chromium when needed.
|
|||
|
||||
- `cd` to the WooCommerce plugin folder
|
||||
|
||||
- `git checkout master` or checkout the branch where you need to run tests
|
||||
- `git checkout trunk` or checkout the branch where you need to run tests
|
||||
|
||||
- Run `nvm use`
|
||||
|
||||
|
@ -267,7 +267,7 @@ In the WooCommerce Core repository the tests are in `tests/e2e/core-tests/specs/
|
|||
The following packages are used in write tests:
|
||||
|
||||
- `@automattic/puppeteer-utils` - utilities and configuration for running puppeteer against WordPress. See details in the [package's repository](https://github.com/Automattic/puppeteer-utils).
|
||||
- `@woocommerce/e2e-utils` - this package contains utilities to simplify writing e2e tests specific to WooCommmerce. See details in the [package's repository](https://github.com/woocommerce/woocommerce/tree/master/tests/e2e/utils).
|
||||
- `@woocommerce/e2e-utils` - this package contains utilities to simplify writing e2e tests specific to WooCommmerce. See details in the [package's repository](https://github.com/woocommerce/woocommerce/tree/trunk/tests/e2e/utils).
|
||||
|
||||
### Creating test structure
|
||||
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
# Unreleased
|
||||
|
||||
## Added
|
||||
|
||||
- Support for the external product type.
|
||||
- Added support for grouped product type
|
||||
- Support for coupons.
|
||||
|
||||
# 0.1.1
|
||||
|
||||
## Breaking Changes
|
||||
|
@ -14,7 +20,7 @@
|
|||
|
||||
## Changes
|
||||
|
||||
- Added a tranformation layer between API responses and internal models
|
||||
- Added a transformation layer between API responses and internal models
|
||||
|
||||
## Fixed
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"version": "0.1.1",
|
||||
"author": "Automattic",
|
||||
"description": "A simple interface for interacting with a WooCommerce installation.",
|
||||
"homepage": "https://github.com/woocommerce/woocommerce/tree/master/tests/e2e/api/README.md",
|
||||
"homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/tests/e2e/api/README.md",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/woocommerce/woocommerce.git"
|
||||
|
|
|
@ -0,0 +1,243 @@
|
|||
import { Model } from '../model';
|
||||
import { HTTPClient } from '../../http';
|
||||
import { couponRESTRepository } from '../../repositories';
|
||||
import {
|
||||
ModelRepositoryParams,
|
||||
CreatesModels,
|
||||
ListsModels,
|
||||
ReadsModels,
|
||||
UpdatesModels,
|
||||
DeletesModels,
|
||||
} from '../../framework';
|
||||
import {
|
||||
CouponUpdateParams,
|
||||
} from './shared';
|
||||
|
||||
/**
|
||||
* The parameters embedded in this generic can be used in the ModelRepository in order to give
|
||||
* type-safety in an incredibly granular way.
|
||||
*/
|
||||
export type CouponRepositoryParams = ModelRepositoryParams< Coupon, never, never, CouponUpdateParams >;
|
||||
|
||||
/**
|
||||
* An interface for creating coupons using the repository.
|
||||
*
|
||||
* @typedef CreatesCoupons
|
||||
* @alias CreatesModels.<Coupon>
|
||||
*/
|
||||
export type CreatesCoupons = CreatesModels< CouponRepositoryParams >;
|
||||
|
||||
/**
|
||||
* An interface for reading coupons using the repository.
|
||||
*
|
||||
* @typedef ReadsCoupons
|
||||
* @alias ReadsModels.<Coupon>
|
||||
*/
|
||||
export type ReadsCoupons = ReadsModels< CouponRepositoryParams >;
|
||||
|
||||
/**
|
||||
* An interface for updating coupons using the repository.
|
||||
*
|
||||
* @typedef UpdatesCoupons
|
||||
* @alias UpdatesModels.<Coupon>
|
||||
*/
|
||||
export type UpdatesCoupons = UpdatesModels< CouponRepositoryParams >;
|
||||
|
||||
/**
|
||||
* An interface for listing coupons using the repository.
|
||||
*
|
||||
* @typedef ListsCoupons
|
||||
* @alias ListsModels.<Coupon>
|
||||
*/
|
||||
export type ListsCoupons = ListsModels< CouponRepositoryParams >;
|
||||
|
||||
/**
|
||||
* An interface for deleting coupons using the repository.
|
||||
*
|
||||
* @typedef DeletesCoupons
|
||||
* @alias DeletesModels.<Coupons>
|
||||
*/
|
||||
export type DeletesCoupons = DeletesModels< CouponRepositoryParams >;
|
||||
|
||||
/**
|
||||
* The type of discount that is available for the coupon.
|
||||
*/
|
||||
type DiscountType = 'percent' | 'fixed_cart' | 'fixed_product' | string;
|
||||
|
||||
/**
|
||||
* A coupon object.
|
||||
*/
|
||||
export class Coupon extends Model {
|
||||
/**
|
||||
* The coupon code.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly code: string = '';
|
||||
|
||||
/**
|
||||
* The amount of the discount, must always be numeric.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly amount: string = '';
|
||||
|
||||
/**
|
||||
* The date the coupon was created.
|
||||
*
|
||||
* @type {Date}
|
||||
*/
|
||||
public readonly dateCreated: Date = new Date();
|
||||
|
||||
/**
|
||||
* The date the coupon was modified.
|
||||
*
|
||||
* @type {Date}
|
||||
*/
|
||||
public readonly dateModified: Date = new Date();
|
||||
|
||||
/**
|
||||
* The discount type for the coupon.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly discountType: string | DiscountType = '';
|
||||
|
||||
/**
|
||||
* The description of the coupon.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly description: string = '';
|
||||
|
||||
/**
|
||||
* The date the coupon expires.
|
||||
*
|
||||
* @type {Date}
|
||||
*/
|
||||
public readonly dateExpires: Date = new Date();
|
||||
|
||||
/**
|
||||
* The number of times the coupon has already been used.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
public readonly usageCount: Number = 0;
|
||||
|
||||
/**
|
||||
* Flags if the coupon can only be used on its own and not combined with other coupons.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
public readonly individualUse: boolean = false;
|
||||
|
||||
/**
|
||||
* List of Product IDs that the coupon can be applied to.
|
||||
*
|
||||
* @type {ReadonlyArray.<number>}
|
||||
*/
|
||||
public readonly productIds: Array<number> = [];
|
||||
|
||||
/**
|
||||
* List of Product IDs that the coupon cannot be applied to.
|
||||
*
|
||||
* @type {ReadonlyArray.<number>}
|
||||
*/
|
||||
public readonly excludedProductIds: Array<number> = [];
|
||||
|
||||
/**
|
||||
* How many times the coupon can be used.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
public readonly usageLimit: Number = -1;
|
||||
|
||||
/**
|
||||
* How many times the coupon can be used per customer.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
public readonly usageLimitPerUser: Number = -1;
|
||||
|
||||
/**
|
||||
* Max number of items in the cart the coupon can be applied to.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
public readonly limitUsageToXItems: Number = -1;
|
||||
|
||||
/**
|
||||
* Flags if the free shipping option requires a coupon. This coupon will enable free shipping.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
public readonly freeShipping: boolean = false;
|
||||
|
||||
/**
|
||||
* List of Category IDs the coupon applies to.
|
||||
*
|
||||
* @type {ReadonlyArray.<number>}
|
||||
*/
|
||||
public readonly productCategories: Array<number> = [];
|
||||
|
||||
/**
|
||||
* List of Category IDs the coupon does not apply to.
|
||||
*
|
||||
* @type {ReadonlyArray.<number>}
|
||||
*/
|
||||
public readonly excludedProductCategories: Array<number> = [];
|
||||
|
||||
/**
|
||||
* Flags if the coupon applies to items on sale.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
public readonly excludeSaleItems: boolean = false;
|
||||
|
||||
/**
|
||||
* The minimum order amount that needs to be in the cart before the coupon applies.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly minimumAmount: string = '';
|
||||
|
||||
/**
|
||||
* The maximum order amount allowed when using the coupon.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly maximumAmount: string = '';
|
||||
|
||||
/**
|
||||
* List of email addresses that can use this coupon.
|
||||
*
|
||||
* @type {ReadonlyArray.<string>}
|
||||
*/
|
||||
public readonly emailRestrictions: Array<string> = [];
|
||||
|
||||
/**
|
||||
* List of user IDs (or guest emails) that have used the coupon.
|
||||
*
|
||||
* @type {ReadonlyArray.<string>}
|
||||
*/
|
||||
public readonly usedBy: Array<string> = [];
|
||||
|
||||
/**
|
||||
* Creates a new coupon instance with the given properties
|
||||
*
|
||||
* @param {Object} properties The properties to set in the object.
|
||||
*/
|
||||
public constructor( properties?: Partial< Coupon > ) {
|
||||
super();
|
||||
Object.assign( this, properties );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the repository for interacting with this type of model.
|
||||
*
|
||||
* @param {HTTPClient} httpClient The client for communicating via HTTP.
|
||||
*/
|
||||
public static restRepository( httpClient: HTTPClient ): ReturnType< typeof couponRESTRepository > {
|
||||
return couponRESTRepository( httpClient );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export * from './coupon';
|
||||
export * from './shared';
|
|
@ -0,0 +1 @@
|
|||
export * from './update-params';
|
|
@ -0,0 +1,7 @@
|
|||
/**
|
||||
* Coupon properties that can be updated
|
||||
*/
|
||||
export type CouponUpdateParams = 'code' | 'amount' | 'description' | 'discountType' | 'dateExpires' | 'individualUse'
|
||||
| 'usageCount' | 'productIds' | 'excludedProductIds' | 'usageLimit' | 'usageLimitPerUser' | 'limitUsageToXItems'
|
||||
| 'freeShipping' | 'productCategories' | 'excludedProductCategories' | 'excludeSaleItems' | 'minimumAmount'
|
||||
| 'maximumAmount' | 'emailRestrictions';
|
|
@ -2,3 +2,4 @@ export * from './products';
|
|||
export * from './model';
|
||||
export * from './settings';
|
||||
export * from './shared-types';
|
||||
export * from './coupons';
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { AbstractProductData } from './data';
|
||||
import { ModelID } from '../../model';
|
||||
import {
|
||||
CatalogVisibility,
|
||||
ProductTerm,
|
||||
|
@ -10,6 +11,21 @@ import {
|
|||
*/
|
||||
export type ProductSearchParams = { search: string };
|
||||
|
||||
/**
|
||||
* The base product URL.
|
||||
*
|
||||
* @return {string} RESTful Url.
|
||||
*/
|
||||
export const baseProductURL = () => '/wc/v3/products/';
|
||||
|
||||
/**
|
||||
* A common product URL builder.
|
||||
*
|
||||
* @param {ModelID} id the id of the product.
|
||||
* @return {string} RESTful Url.
|
||||
*/
|
||||
export const buildProductURL = ( id: ModelID ) => baseProductURL() + id;
|
||||
|
||||
/**
|
||||
* The base for all product types.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import { Model } from '../../model';
|
||||
|
||||
/**
|
||||
* The base for external products.
|
||||
*/
|
||||
abstract class AbstractProductExternal extends Model {
|
||||
/**
|
||||
* The product's button text.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly buttonText: string = ''
|
||||
|
||||
/**
|
||||
* The product's external URL.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly externalUrl: string = ''
|
||||
}
|
||||
|
||||
export interface IProductExternal extends AbstractProductExternal {}
|
|
@ -0,0 +1,15 @@
|
|||
import { Model } from '../../model';
|
||||
|
||||
/**
|
||||
* The base for cross sells.
|
||||
*/
|
||||
abstract class AbstractProductGrouped extends Model {
|
||||
/**
|
||||
* An array of grouped product ids.
|
||||
*
|
||||
* @type {ReadonlyArray.<number>}
|
||||
*/
|
||||
public readonly groupedProducts: Array<number> = [];
|
||||
}
|
||||
|
||||
export interface IProductGrouped extends AbstractProductGrouped {}
|
|
@ -2,7 +2,10 @@ export * from './common';
|
|||
export * from './cross-sell';
|
||||
export * from './data';
|
||||
export * from './delivery';
|
||||
export * from './external';
|
||||
export * from './grouped';
|
||||
export * from './inventory';
|
||||
export * from './price';
|
||||
export * from './sales-tax';
|
||||
export * from './shipping';
|
||||
export * from './upsell';
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
import { Model } from '../../model';
|
||||
|
||||
/**
|
||||
* The base for price properties.
|
||||
*/
|
||||
abstract class AbstractProductPrice extends Model {
|
||||
/**
|
||||
* The current price of the product.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly price: string = '';
|
||||
|
||||
/**
|
||||
* The rendered HTML for the current price of the product.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly priceHtml: string = '';
|
||||
|
||||
/**
|
||||
* The regular price of the product when not discounted.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly regularPrice: string = '';
|
||||
|
||||
/**
|
||||
* Indicates whether or not the product is currently on sale.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
public readonly onSale: boolean = false;
|
||||
|
||||
/**
|
||||
* The price of the product when on sale.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly salePrice: string = '';
|
||||
|
||||
/**
|
||||
* The GMT datetime when the product should start to be on sale.
|
||||
*
|
||||
* @type {Date|null}
|
||||
*/
|
||||
public readonly saleStart: Date | null = null;
|
||||
|
||||
/**
|
||||
* The GMT datetime when the product should no longer be on sale.
|
||||
*
|
||||
* @type {Date|null}
|
||||
*/
|
||||
public readonly saleEnd: Date | null = null;
|
||||
}
|
||||
|
||||
export interface IProductPrice extends AbstractProductPrice {}
|
|
@ -0,0 +1,139 @@
|
|||
import {
|
||||
AbstractProduct,
|
||||
IProductCommon,
|
||||
IProductExternal,
|
||||
IProductPrice,
|
||||
IProductSalesTax,
|
||||
IProductUpSells,
|
||||
ProductSearchParams,
|
||||
} from './abstract';
|
||||
import {
|
||||
ProductCommonUpdateParams,
|
||||
ProductExternalUpdateParams,
|
||||
ProductPriceUpdateParams,
|
||||
ProductSalesTaxUpdateParams,
|
||||
ProductUpSellUpdateParams,
|
||||
Taxability,
|
||||
} from './shared';
|
||||
import { HTTPClient } from '../../http';
|
||||
import { externalProductRESTRepository } from '../../repositories';
|
||||
import {
|
||||
CreatesModels,
|
||||
DeletesModels,
|
||||
ListsModels,
|
||||
ModelRepositoryParams,
|
||||
ReadsModels,
|
||||
UpdatesModels,
|
||||
} from '../../framework';
|
||||
|
||||
/**
|
||||
* The parameters that external products can update.
|
||||
*/
|
||||
type ExternalProductUpdateParams = ProductCommonUpdateParams
|
||||
& ProductExternalUpdateParams
|
||||
& ProductPriceUpdateParams
|
||||
& ProductSalesTaxUpdateParams
|
||||
& ProductUpSellUpdateParams;
|
||||
/**
|
||||
* The parameters embedded in this generic can be used in the ModelRepository in order to give
|
||||
* type-safety in an incredibly granular way.
|
||||
*/
|
||||
export type ExternalProductRepositoryParams =
|
||||
ModelRepositoryParams< ExternalProduct, never, ProductSearchParams, ExternalProductUpdateParams >;
|
||||
|
||||
/**
|
||||
* An interface for listing external products using the repository.
|
||||
*
|
||||
* @typedef ListsExternalProducts
|
||||
* @alias ListsModels.<ExternalProduct>
|
||||
*/
|
||||
export type ListsExternalProducts = ListsModels< ExternalProductRepositoryParams >;
|
||||
|
||||
/**
|
||||
* An interface for external simple products using the repository.
|
||||
*
|
||||
* @typedef CreatesExternalProducts
|
||||
* @alias CreatesModels.<ExternalProduct>
|
||||
*/
|
||||
export type CreatesExternalProducts = CreatesModels< ExternalProductRepositoryParams >;
|
||||
|
||||
/**
|
||||
* An interface for reading external products using the repository.
|
||||
*
|
||||
* @typedef ReadsExternalProducts
|
||||
* @alias ReadsModels.<ExternalProduct>
|
||||
*/
|
||||
export type ReadsExternalProducts = ReadsModels< ExternalProductRepositoryParams >;
|
||||
|
||||
/**
|
||||
* An interface for updating external products using the repository.
|
||||
*
|
||||
* @typedef UpdatesExternalProducts
|
||||
* @alias UpdatesModels.<ExternalProduct>
|
||||
*/
|
||||
export type UpdatesExternalProducts = UpdatesModels< ExternalProductRepositoryParams >;
|
||||
|
||||
/**
|
||||
* An interface for deleting external products using the repository.
|
||||
*
|
||||
* @typedef DeletesExternalProducts
|
||||
* @alias DeletesModels.<ExternalProduct>
|
||||
*/
|
||||
export type DeletesExternalProducts = DeletesModels< ExternalProductRepositoryParams >;
|
||||
|
||||
/**
|
||||
* The base for the external product object.
|
||||
*/
|
||||
export class ExternalProduct extends AbstractProduct implements
|
||||
IProductCommon,
|
||||
IProductExternal,
|
||||
IProductPrice,
|
||||
IProductSalesTax,
|
||||
IProductUpSells {
|
||||
/**
|
||||
* @see ./abstracts/external.ts
|
||||
*/
|
||||
public readonly buttonText: string = ''
|
||||
public readonly externalUrl: string = ''
|
||||
|
||||
/**
|
||||
* @see ./abstracts/price.ts
|
||||
*/
|
||||
public readonly price: string = '';
|
||||
public readonly priceHtml: string = '';
|
||||
public readonly regularPrice: string = '';
|
||||
public readonly onSale: boolean = false;
|
||||
public readonly salePrice: string = '';
|
||||
public readonly saleStart: Date | null = null;
|
||||
public readonly saleEnd: Date | null = null;
|
||||
|
||||
/**
|
||||
* @see ./abstracts/upsell.ts
|
||||
*/
|
||||
public readonly upSellIds: Array<number> = [];
|
||||
|
||||
/**
|
||||
* @see ./abstracts/sales-tax.ts
|
||||
*/
|
||||
public readonly taxStatus: Taxability = Taxability.ProductAndShipping;
|
||||
public readonly taxClass: string = '';
|
||||
|
||||
/**
|
||||
* Creates a new simple product instance with the given properties
|
||||
*
|
||||
* @param {Object} properties The properties to set in the object.
|
||||
*/
|
||||
public constructor( properties?: Partial< ExternalProduct > ) {
|
||||
super();
|
||||
Object.assign( this, properties );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a model repository configured for communicating via the REST API.
|
||||
*
|
||||
* @param {HTTPClient} httpClient The client for communicating via HTTP.
|
||||
*/
|
||||
public static restRepository( httpClient: HTTPClient ): ReturnType< typeof externalProductRESTRepository > {
|
||||
return externalProductRESTRepository( httpClient );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
import {
|
||||
AbstractProduct,
|
||||
IProductCommon,
|
||||
IProductGrouped,
|
||||
IProductUpSells,
|
||||
ProductSearchParams,
|
||||
} from './abstract';
|
||||
import {
|
||||
ProductCommonUpdateParams,
|
||||
ProductGroupedUpdateParams,
|
||||
ProductUpSellUpdateParams,
|
||||
} from './shared';
|
||||
import { HTTPClient } from '../../http';
|
||||
import { groupedProductRESTRepository } from '../../repositories';
|
||||
import {
|
||||
CreatesModels,
|
||||
DeletesModels,
|
||||
ListsModels,
|
||||
ModelRepositoryParams,
|
||||
ReadsModels,
|
||||
UpdatesModels,
|
||||
} from '../../framework';
|
||||
|
||||
/**
|
||||
* The parameters that Grouped products can update.
|
||||
*/
|
||||
type GroupedProductUpdateParams = ProductCommonUpdateParams
|
||||
& ProductGroupedUpdateParams
|
||||
& ProductUpSellUpdateParams;
|
||||
/**
|
||||
* The parameters embedded in this generic can be used in the ModelRepository in order to give
|
||||
* type-safety in an incredibly granular way.
|
||||
*/
|
||||
export type GroupedProductRepositoryParams =
|
||||
ModelRepositoryParams< GroupedProduct, never, ProductSearchParams, GroupedProductUpdateParams >;
|
||||
|
||||
/**
|
||||
* An interface for listing Grouped products using the repository.
|
||||
*
|
||||
* @typedef ListsGroupedProducts
|
||||
* @alias ListsModels.<GroupedProduct>
|
||||
*/
|
||||
export type ListsGroupedProducts = ListsModels< GroupedProductRepositoryParams >;
|
||||
|
||||
/**
|
||||
* An interface for creating Grouped products using the repository.
|
||||
*
|
||||
* @typedef CreatesGroupedProducts
|
||||
* @alias CreatesModels.<GroupedProduct>
|
||||
*/
|
||||
export type CreatesGroupedProducts = CreatesModels< GroupedProductRepositoryParams >;
|
||||
|
||||
/**
|
||||
* An interface for reading Grouped products using the repository.
|
||||
*
|
||||
* @typedef ReadsGroupedProducts
|
||||
* @alias ReadsModels.<GroupedProduct>
|
||||
*/
|
||||
export type ReadsGroupedProducts = ReadsModels< GroupedProductRepositoryParams >;
|
||||
|
||||
/**
|
||||
* An interface for updating Grouped products using the repository.
|
||||
*
|
||||
* @typedef UpdatesGroupedProducts
|
||||
* @alias UpdatesModels.<GroupedProduct>
|
||||
*/
|
||||
export type UpdatesGroupedProducts = UpdatesModels< GroupedProductRepositoryParams >;
|
||||
|
||||
/**
|
||||
* An interface for deleting Grouped products using the repository.
|
||||
*
|
||||
* @typedef DeletesGroupedProducts
|
||||
* @alias DeletesModels.<GroupedProduct>
|
||||
*/
|
||||
export type DeletesGroupedProducts = DeletesModels< GroupedProductRepositoryParams >;
|
||||
|
||||
/**
|
||||
* The base for the Grouped product object.
|
||||
*/
|
||||
export class GroupedProduct extends AbstractProduct implements
|
||||
IProductCommon,
|
||||
IProductGrouped,
|
||||
IProductUpSells {
|
||||
/**
|
||||
* @see ./abstracts/grouped.ts
|
||||
*/
|
||||
public readonly groupedProducts: Array<number> = [];
|
||||
|
||||
/**
|
||||
* @see ./abstracts/upsell.ts
|
||||
*/
|
||||
public readonly upSellIds: Array<number> = [];
|
||||
|
||||
/**
|
||||
* Creates a new Grouped product instance with the given properties
|
||||
*
|
||||
* @param {Object} properties The properties to set in the object.
|
||||
*/
|
||||
public constructor( properties?: Partial< GroupedProduct > ) {
|
||||
super();
|
||||
Object.assign( this, properties );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a model repository configured for communicating via the REST API.
|
||||
*
|
||||
* @param {HTTPClient} httpClient The client for communicating via HTTP.
|
||||
*/
|
||||
public static restRepository( httpClient: HTTPClient ): ReturnType< typeof groupedProductRESTRepository > {
|
||||
return groupedProductRESTRepository( httpClient );
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
export * from './abstract';
|
||||
export * from './shared';
|
||||
export * from './simple-product';
|
||||
export * from './grouped-product';
|
||||
export * from './external-product';
|
||||
export * from './variation';
|
||||
export * from './variable-product';
|
||||
|
|
|
@ -28,6 +28,12 @@ export type ProductCommonUpdateParams = 'name' | 'slug' | 'shortDescription'
|
|||
*/
|
||||
export type ProductCrossUpdateParams = 'crossSellIds';
|
||||
|
||||
/**
|
||||
* Price properties.
|
||||
*/
|
||||
export type ProductPriceUpdateParams = 'price' | 'priceHtml' | 'regularPrice'
|
||||
| 'salePrice' | 'saleStart' | 'saleEnd';
|
||||
|
||||
/**
|
||||
* Upsells property.
|
||||
*/
|
||||
|
@ -36,12 +42,12 @@ export type ProductUpSellUpdateParams = 'upSellIds';
|
|||
/**
|
||||
* Properties exclusive to the External product type.
|
||||
*/
|
||||
export type ProductExternalTypeUpdateParams = 'buttonText' | 'externalUrl';
|
||||
export type ProductExternalUpdateParams = 'buttonText' | 'externalUrl';
|
||||
|
||||
/**
|
||||
* Properties exclusive to the Grouped product type.
|
||||
*/
|
||||
export type ProductGroupedTypeUpdateParams = 'groupedProducts';
|
||||
export type ProductGroupedUpdateParams = 'groupedProducts';
|
||||
|
||||
/**
|
||||
* Properties related to tracking inventory.
|
||||
|
|
|
@ -4,17 +4,19 @@ import {
|
|||
IProductCrossSells,
|
||||
IProductDelivery,
|
||||
IProductInventory,
|
||||
IProductPrice,
|
||||
IProductSalesTax,
|
||||
IProductShipping,
|
||||
IProductUpSells,
|
||||
ProductSearchParams,
|
||||
} from './abstract';
|
||||
import {
|
||||
ProductCommonUpdateParams,
|
||||
ProductCrossUpdateParams,
|
||||
ProductDeliveryUpdateParams,
|
||||
ProductInventoryUpdateParams,
|
||||
ProductCommonUpdateParams,
|
||||
ProductPriceUpdateParams,
|
||||
ProductSalesTaxUpdateParams,
|
||||
ProductCrossUpdateParams,
|
||||
ProductShippingUpdateParams,
|
||||
ProductUpSellUpdateParams,
|
||||
ProductDownload,
|
||||
|
@ -40,6 +42,7 @@ type SimpleProductUpdateParams = ProductDeliveryUpdateParams
|
|||
& ProductCommonUpdateParams
|
||||
& ProductCrossUpdateParams
|
||||
& ProductInventoryUpdateParams
|
||||
& ProductPriceUpdateParams
|
||||
& ProductSalesTaxUpdateParams
|
||||
& ProductShippingUpdateParams
|
||||
& ProductUpSellUpdateParams;
|
||||
|
@ -97,6 +100,7 @@ export class SimpleProduct extends AbstractProduct implements
|
|||
IProductCrossSells,
|
||||
IProductDelivery,
|
||||
IProductInventory,
|
||||
IProductPrice,
|
||||
IProductSalesTax,
|
||||
IProductShipping,
|
||||
IProductUpSells {
|
||||
|
@ -131,6 +135,17 @@ export class SimpleProduct extends AbstractProduct implements
|
|||
public readonly canBackorder: boolean = false;
|
||||
public readonly isOnBackorder: boolean = false;
|
||||
|
||||
/**
|
||||
* @see ./abstracts/price.ts
|
||||
*/
|
||||
public readonly price: string = '';
|
||||
public readonly priceHtml: string = '';
|
||||
public readonly regularPrice: string = '';
|
||||
public readonly onSale: boolean = false;
|
||||
public readonly salePrice: string = '';
|
||||
public readonly saleStart: Date | null = null;
|
||||
public readonly saleEnd: Date | null = null;
|
||||
|
||||
/**
|
||||
* @see ./abstracts/sales-tax.ts
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
import { HTTPClient } from '../../../http';
|
||||
import {
|
||||
ModelRepository,
|
||||
} from '../../../framework';
|
||||
import {
|
||||
ModelID,
|
||||
Coupon,
|
||||
CouponRepositoryParams,
|
||||
ListsCoupons,
|
||||
ReadsCoupons,
|
||||
UpdatesCoupons,
|
||||
CreatesCoupons,
|
||||
DeletesCoupons,
|
||||
} from '../../../models';
|
||||
|
||||
import {
|
||||
restList,
|
||||
restCreate,
|
||||
restRead,
|
||||
restUpdate,
|
||||
restDelete,
|
||||
} from '../shared';
|
||||
import { createCouponTransformer } from './transformer';
|
||||
|
||||
/**
|
||||
* Creates a new ModelRepository instance for interacting with models via the REST API.
|
||||
*
|
||||
* @param {HTTPClient} httpClient The HTTP client for the REST requests to be made using.
|
||||
* @return {
|
||||
* CreatesCoupons|
|
||||
* ListsCoupons|
|
||||
* ReadsCoupons|
|
||||
* UpdatesCoupons |
|
||||
* DeletesCoupons
|
||||
* } The created repository.
|
||||
*/
|
||||
export default function couponRESTRepository( httpClient: HTTPClient ): CreatesCoupons
|
||||
& ListsCoupons
|
||||
& ReadsCoupons
|
||||
& UpdatesCoupons
|
||||
& DeletesCoupons {
|
||||
const buildURL = ( id: ModelID ) => '/wc/v3/coupons/' + id;
|
||||
// Using `?force=true` permanently deletes the coupon
|
||||
const buildDeleteUrl = ( id: ModelID ) => `/wc/v3/coupons/${ id }?force=true`;
|
||||
|
||||
const transformer = createCouponTransformer();
|
||||
|
||||
return new ModelRepository(
|
||||
restList< CouponRepositoryParams >( () => '/wc/v3/coupons', Coupon, httpClient, transformer ),
|
||||
restCreate< CouponRepositoryParams >( () => '/wc/v3/coupons', Coupon, httpClient, transformer ),
|
||||
restRead< CouponRepositoryParams >( buildURL, Coupon, httpClient, transformer ),
|
||||
restUpdate< CouponRepositoryParams >( buildURL, Coupon, httpClient, transformer ),
|
||||
restDelete< CouponRepositoryParams >( buildDeleteUrl, httpClient ),
|
||||
);
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
import couponRESTRepository from './coupon';
|
||||
|
||||
export { couponRESTRepository };
|
|
@ -0,0 +1,64 @@
|
|||
import {
|
||||
IgnorePropertyTransformation,
|
||||
KeyChangeTransformation,
|
||||
ModelTransformer,
|
||||
PropertyType,
|
||||
PropertyTypeTransformation,
|
||||
} from '../../../framework';
|
||||
|
||||
import { Coupon } from '../../../models';
|
||||
|
||||
/**
|
||||
* Creates a transformer for a coupon object.
|
||||
*
|
||||
* @return {ModelTransformer} The created transformer.
|
||||
*/
|
||||
export function createCouponTransformer(): ModelTransformer< Coupon > {
|
||||
return new ModelTransformer(
|
||||
[
|
||||
new IgnorePropertyTransformation( [ 'date_created', 'date_modified' ] ),
|
||||
new PropertyTypeTransformation(
|
||||
{
|
||||
code: PropertyType.String,
|
||||
amount: PropertyType.String,
|
||||
dateCreated: PropertyType.Date,
|
||||
dateModified: PropertyType.Date,
|
||||
discountType: PropertyType.String,
|
||||
dateExpires: PropertyType.Date,
|
||||
usageCount: PropertyType.Integer,
|
||||
individualUse: PropertyType.Boolean,
|
||||
usageLimit: PropertyType.Integer,
|
||||
usageLimitPerUser: PropertyType.Integer,
|
||||
limitUsageToXItems: PropertyType.Integer,
|
||||
freeShipping: PropertyType.Boolean,
|
||||
excludeSaleItems: PropertyType.Boolean,
|
||||
minimumAmount: PropertyType.String,
|
||||
maximumAmount: PropertyType.String,
|
||||
},
|
||||
),
|
||||
new KeyChangeTransformation< Coupon >(
|
||||
{
|
||||
dateCreated: 'date_created_gmt',
|
||||
dateModified: 'date_modified_gmt',
|
||||
discountType: 'discount_type',
|
||||
dateExpires: 'date_expires',
|
||||
usageCount: 'usage_count',
|
||||
individualUse: 'individual_use',
|
||||
productIds: 'product_ids',
|
||||
excludedProductIds: 'excluded_product_ids',
|
||||
usageLimit: 'usage_limit',
|
||||
usageLimitPerUser: 'usage_limit_per_user',
|
||||
limitUsageToXItems: 'limit_usage_to_x_items',
|
||||
freeShipping: 'free_shipping',
|
||||
productCategories: 'product_categories',
|
||||
excludedProductCategories: 'excluded_product_categories',
|
||||
excludeSaleItems: 'exclude_sale_items',
|
||||
minimumAmount: 'minimum_amount',
|
||||
maximumAmount: 'maximum_amount',
|
||||
emailRestrictions: 'email_restrictions',
|
||||
usedBy: 'used_by',
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
|
@ -1,2 +1,3 @@
|
|||
export * from './products';
|
||||
export * from './settings';
|
||||
export * from './coupons';
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
import { HTTPClient } from '../../../http';
|
||||
import { ModelRepository } from '../../../framework';
|
||||
import {
|
||||
baseProductURL,
|
||||
buildProductURL,
|
||||
ExternalProduct,
|
||||
CreatesExternalProducts,
|
||||
DeletesExternalProducts,
|
||||
ListsExternalProducts,
|
||||
ReadsExternalProducts,
|
||||
ExternalProductRepositoryParams,
|
||||
UpdatesExternalProducts,
|
||||
} from '../../../models';
|
||||
import {
|
||||
createProductTransformer,
|
||||
createProductExternalTransformation,
|
||||
createProductPriceTransformation,
|
||||
createProductSalesTaxTransformation,
|
||||
createProductUpSellsTransformation,
|
||||
} from './shared';
|
||||
import {
|
||||
restCreate,
|
||||
restDelete,
|
||||
restList,
|
||||
restRead,
|
||||
restUpdate,
|
||||
} from '../shared';
|
||||
|
||||
/**
|
||||
* Creates a new ModelRepository instance for interacting with models via the REST API.
|
||||
*
|
||||
* @param {HTTPClient} httpClient The HTTP client for the REST requests to be made using.
|
||||
* @return {
|
||||
* ListsExternalProducts|
|
||||
* CreatesExternalProducts|
|
||||
* ReadsExternalProducts|
|
||||
* UpdatesExternalProducts|
|
||||
* DeletesExternalProducts
|
||||
* } The created repository.
|
||||
*/
|
||||
export function externalProductRESTRepository( httpClient: HTTPClient ): ListsExternalProducts
|
||||
& CreatesExternalProducts
|
||||
& ReadsExternalProducts
|
||||
& UpdatesExternalProducts
|
||||
& DeletesExternalProducts {
|
||||
const external = createProductExternalTransformation();
|
||||
const price = createProductPriceTransformation();
|
||||
const salesTax = createProductSalesTaxTransformation();
|
||||
const upsells = createProductUpSellsTransformation();
|
||||
const transformations = [
|
||||
...external,
|
||||
...price,
|
||||
...salesTax,
|
||||
...upsells,
|
||||
];
|
||||
|
||||
const transformer = createProductTransformer<ExternalProduct>( 'external', transformations );
|
||||
|
||||
return new ModelRepository(
|
||||
restList< ExternalProductRepositoryParams >( baseProductURL, ExternalProduct, httpClient, transformer ),
|
||||
restCreate< ExternalProductRepositoryParams >( baseProductURL, ExternalProduct, httpClient, transformer ),
|
||||
restRead< ExternalProductRepositoryParams >( buildProductURL, ExternalProduct, httpClient, transformer ),
|
||||
restUpdate< ExternalProductRepositoryParams >( buildProductURL, ExternalProduct, httpClient, transformer ),
|
||||
restDelete< ExternalProductRepositoryParams >( buildProductURL, httpClient ),
|
||||
);
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
import { HTTPClient } from '../../../http';
|
||||
import { ModelRepository } from '../../../framework';
|
||||
import {
|
||||
GroupedProduct,
|
||||
CreatesGroupedProducts,
|
||||
DeletesGroupedProducts,
|
||||
ListsGroupedProducts,
|
||||
ReadsGroupedProducts,
|
||||
GroupedProductRepositoryParams,
|
||||
UpdatesGroupedProducts,
|
||||
baseProductURL,
|
||||
buildProductURL,
|
||||
} from '../../../models';
|
||||
import {
|
||||
createProductTransformer,
|
||||
createProductGroupedTransformation,
|
||||
createProductUpSellsTransformation,
|
||||
} from './shared';
|
||||
import {
|
||||
restCreate,
|
||||
restDelete,
|
||||
restList,
|
||||
restRead,
|
||||
restUpdate,
|
||||
} from '../shared';
|
||||
|
||||
/**
|
||||
* Creates a new ModelRepository instance for interacting with models via the REST API.
|
||||
*
|
||||
* @param {HTTPClient} httpClient The HTTP client for the REST requests to be made using.
|
||||
* @return {
|
||||
* ListsGroupedProducts|
|
||||
* CreatesGroupedProducts|
|
||||
* ReadsGroupedProducts|
|
||||
* UpdatesGroupedProducts|
|
||||
* DeletesGroupedProducts
|
||||
* } The created repository.
|
||||
*/
|
||||
export function groupedProductRESTRepository( httpClient: HTTPClient ): ListsGroupedProducts
|
||||
& CreatesGroupedProducts
|
||||
& ReadsGroupedProducts
|
||||
& UpdatesGroupedProducts
|
||||
& DeletesGroupedProducts {
|
||||
const upsells = createProductUpSellsTransformation();
|
||||
const grouped = createProductGroupedTransformation();
|
||||
const transformations = [
|
||||
...upsells,
|
||||
...grouped,
|
||||
];
|
||||
|
||||
const transformer = createProductTransformer<GroupedProduct>( 'grouped', transformations );
|
||||
|
||||
return new ModelRepository(
|
||||
restList< GroupedProductRepositoryParams >( baseProductURL, GroupedProduct, httpClient, transformer ),
|
||||
restCreate< GroupedProductRepositoryParams >( baseProductURL, GroupedProduct, httpClient, transformer ),
|
||||
restRead< GroupedProductRepositoryParams >( buildProductURL, GroupedProduct, httpClient, transformer ),
|
||||
restUpdate< GroupedProductRepositoryParams >( buildProductURL, GroupedProduct, httpClient, transformer ),
|
||||
restDelete< GroupedProductRepositoryParams >( buildProductURL, httpClient ),
|
||||
);
|
||||
}
|
|
@ -1,9 +1,13 @@
|
|||
import { createProductTransformer } from './shared';
|
||||
import { groupedProductRESTRepository } from './grouped-product';
|
||||
import { simpleProductRESTRepository } from './simple-product';
|
||||
import { externalProductRESTRepository } from './external-product';
|
||||
import { variableProductRESTRepository } from './variable-product';
|
||||
|
||||
export {
|
||||
createProductTransformer,
|
||||
externalProductRESTRepository,
|
||||
groupedProductRESTRepository,
|
||||
simpleProductRESTRepository,
|
||||
variableProductRESTRepository,
|
||||
};
|
||||
|
|
|
@ -15,7 +15,10 @@ import {
|
|||
AbstractProductData,
|
||||
IProductCrossSells,
|
||||
IProductDelivery,
|
||||
IProductExternal,
|
||||
IProductGrouped,
|
||||
IProductInventory,
|
||||
IProductPrice,
|
||||
IProductSalesTax,
|
||||
IProductShipping,
|
||||
IProductUpSells,
|
||||
|
@ -127,8 +130,6 @@ export function createProductDataTransformer< T extends AbstractProductData >(
|
|||
[
|
||||
'date_created',
|
||||
'date_modified',
|
||||
'date_on_sale_from',
|
||||
'date_on_sale_to',
|
||||
],
|
||||
),
|
||||
new ModelTransformerTransformation( 'attributes', ProductAttribute, createProductAttributeTransformer() ),
|
||||
|
@ -146,6 +147,12 @@ export function createProductDataTransformer< T extends AbstractProductData >(
|
|||
menuOrder: PropertyType.Integer,
|
||||
permalink: PropertyType.String,
|
||||
priceHtml: PropertyType.String,
|
||||
isFeatured: PropertyType.Boolean,
|
||||
allowReviews: PropertyType.Boolean,
|
||||
averageRating: PropertyType.Integer,
|
||||
numRatings: PropertyType.Integer,
|
||||
totalSales: PropertyType.Integer,
|
||||
relatedIds: PropertyType.Integer,
|
||||
},
|
||||
),
|
||||
new KeyChangeTransformation< AbstractProduct >(
|
||||
|
@ -154,15 +161,15 @@ export function createProductDataTransformer< T extends AbstractProductData >(
|
|||
modified: 'date_modified_gmt',
|
||||
postStatus: 'status',
|
||||
isPurchasable: 'purchasable',
|
||||
regularPrice: 'regular_price',
|
||||
onSale: 'on_sale',
|
||||
salePrice: 'sale_price',
|
||||
saleStart: 'date_on_sale_from_gmt',
|
||||
saleEnd: 'date_on_sale_to_gmt',
|
||||
isFeatured: 'featured',
|
||||
catalogVisibility: 'catalog_visibility',
|
||||
allowReviews: 'reviews_allowed',
|
||||
averageRating: 'average_rating',
|
||||
numRatings: 'rating_count',
|
||||
metaData: 'meta_data',
|
||||
parentId: 'parent_id',
|
||||
menuOrder: 'menu_order',
|
||||
priceHtml: 'price_html',
|
||||
relatedIds: 'related_ids',
|
||||
links: '_links',
|
||||
},
|
||||
),
|
||||
|
@ -217,6 +224,43 @@ export function createProductTransformer< T extends AbstractProduct >(
|
|||
return createProductDataTransformer< T >( transformations );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a transformer for the product price properties.
|
||||
*/
|
||||
export function createProductPriceTransformation(): ModelTransformation[] {
|
||||
const transformations = [
|
||||
new IgnorePropertyTransformation(
|
||||
[
|
||||
'date_on_sale_from',
|
||||
'date_on_sale_to',
|
||||
],
|
||||
),
|
||||
new PropertyTypeTransformation(
|
||||
{
|
||||
onSale: PropertyType.Boolean,
|
||||
saleStart: PropertyType.Date,
|
||||
saleEnd: PropertyType.Date,
|
||||
priceHtml: PropertyType.String,
|
||||
},
|
||||
),
|
||||
new KeyChangeTransformation< IProductPrice >(
|
||||
{
|
||||
regularPrice: 'regular_price',
|
||||
onSale: 'on_sale',
|
||||
salePrice: 'sale_price',
|
||||
saleStart: 'date_on_sale_from_gmt',
|
||||
saleEnd: 'date_on_sale_to_gmt',
|
||||
priceHtml: 'price_html',
|
||||
},
|
||||
),
|
||||
];
|
||||
|
||||
return transformations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a transformer for the product cross sells property.
|
||||
*/
|
||||
export function createProductCrossSellsTransformation(): ModelTransformation[] {
|
||||
const transformations = [
|
||||
new PropertyTypeTransformation(
|
||||
|
@ -234,6 +278,9 @@ export function createProductCrossSellsTransformation(): ModelTransformation[] {
|
|||
return transformations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a transformer for the product upsells property.
|
||||
*/
|
||||
export function createProductUpSellsTransformation(): ModelTransformation[] {
|
||||
const transformations = [
|
||||
new PropertyTypeTransformation(
|
||||
|
@ -251,6 +298,29 @@ export function createProductUpSellsTransformation(): ModelTransformation[] {
|
|||
return transformations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformer for the grouped products property.
|
||||
*/
|
||||
export function createProductGroupedTransformation(): ModelTransformation[] {
|
||||
const transformations = [
|
||||
new PropertyTypeTransformation(
|
||||
{
|
||||
groupedProducts: PropertyType.Integer,
|
||||
},
|
||||
),
|
||||
new KeyChangeTransformation< IProductGrouped >(
|
||||
{
|
||||
groupedProducts: 'grouped_products',
|
||||
},
|
||||
),
|
||||
];
|
||||
|
||||
return transformations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a transformer for product delivery properties.
|
||||
*/
|
||||
export function createProductDeliveryTransformation(): ModelTransformation[] {
|
||||
const transformations = [
|
||||
new ModelTransformerTransformation( 'downloads', ProductDownload, createProductDownloadTransformer() ),
|
||||
|
@ -277,6 +347,9 @@ export function createProductDeliveryTransformation(): ModelTransformation[] {
|
|||
return transformations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a transformer for product inventory properties.
|
||||
*/
|
||||
export function createProductInventoryTransformation(): ModelTransformation[] {
|
||||
const transformations = [
|
||||
new PropertyTypeTransformation(
|
||||
|
@ -306,6 +379,9 @@ export function createProductInventoryTransformation(): ModelTransformation[] {
|
|||
return transformations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a transformer for product sales tax properties.
|
||||
*/
|
||||
export function createProductSalesTaxTransformation(): ModelTransformation[] {
|
||||
const transformations = [
|
||||
new PropertyTypeTransformation(
|
||||
|
@ -325,6 +401,9 @@ export function createProductSalesTaxTransformation(): ModelTransformation[] {
|
|||
return transformations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a transformer for product shipping properties.
|
||||
*/
|
||||
export function createProductShippingTransformation(): ModelTransformation[] {
|
||||
const transformations = [
|
||||
new CustomTransformation(
|
||||
|
@ -400,3 +479,24 @@ export function createProductVariableTransformation(): ModelTransformation[] {
|
|||
|
||||
return transformations;
|
||||
}
|
||||
/**
|
||||
* Transformer for the properties unique to the external product type.
|
||||
*/
|
||||
export function createProductExternalTransformation(): ModelTransformation[] {
|
||||
const transformations = [
|
||||
new PropertyTypeTransformation(
|
||||
{
|
||||
buttonText: PropertyType.String,
|
||||
externalUrl: PropertyType.String,
|
||||
},
|
||||
),
|
||||
new KeyChangeTransformation< IProductExternal >(
|
||||
{
|
||||
buttonText: 'button_text',
|
||||
externalUrl: 'external_url',
|
||||
},
|
||||
),
|
||||
];
|
||||
|
||||
return transformations;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,8 @@ import { HTTPClient } from '../../../http';
|
|||
import { ModelRepository } from '../../../framework';
|
||||
import {
|
||||
SimpleProduct,
|
||||
ModelID,
|
||||
baseProductURL,
|
||||
buildProductURL,
|
||||
CreatesSimpleProducts,
|
||||
DeletesSimpleProducts,
|
||||
ListsSimpleProducts,
|
||||
|
@ -15,6 +16,7 @@ import {
|
|||
createProductCrossSellsTransformation,
|
||||
createProductDeliveryTransformation,
|
||||
createProductInventoryTransformation,
|
||||
createProductPriceTransformation,
|
||||
createProductSalesTaxTransformation,
|
||||
createProductShippingTransformation,
|
||||
createProductUpSellsTransformation,
|
||||
|
@ -44,11 +46,10 @@ export function simpleProductRESTRepository( httpClient: HTTPClient ): ListsSimp
|
|||
& ReadsSimpleProducts
|
||||
& UpdatesSimpleProducts
|
||||
& DeletesSimpleProducts {
|
||||
const buildURL = ( id: ModelID ) => '/wc/v3/products/' + id;
|
||||
|
||||
const crossSells = createProductCrossSellsTransformation();
|
||||
const delivery = createProductDeliveryTransformation();
|
||||
const inventory = createProductInventoryTransformation();
|
||||
const price = createProductPriceTransformation();
|
||||
const salesTax = createProductSalesTaxTransformation();
|
||||
const shipping = createProductShippingTransformation();
|
||||
const upsells = createProductUpSellsTransformation();
|
||||
|
@ -56,6 +57,7 @@ export function simpleProductRESTRepository( httpClient: HTTPClient ): ListsSimp
|
|||
...crossSells,
|
||||
...delivery,
|
||||
...inventory,
|
||||
...price,
|
||||
...salesTax,
|
||||
...shipping,
|
||||
...upsells,
|
||||
|
@ -64,10 +66,10 @@ export function simpleProductRESTRepository( httpClient: HTTPClient ): ListsSimp
|
|||
const transformer = createProductTransformer<SimpleProduct>( 'simple', transformations );
|
||||
|
||||
return new ModelRepository(
|
||||
restList< SimpleProductRepositoryParams >( () => '/wc/v3/products', SimpleProduct, httpClient, transformer ),
|
||||
restCreate< SimpleProductRepositoryParams >( () => '/wc/v3/products', SimpleProduct, httpClient, transformer ),
|
||||
restRead< SimpleProductRepositoryParams >( buildURL, SimpleProduct, httpClient, transformer ),
|
||||
restUpdate< SimpleProductRepositoryParams >( buildURL, SimpleProduct, httpClient, transformer ),
|
||||
restDelete< SimpleProductRepositoryParams >( buildURL, httpClient ),
|
||||
restList< SimpleProductRepositoryParams >( baseProductURL, SimpleProduct, httpClient, transformer ),
|
||||
restCreate< SimpleProductRepositoryParams >( baseProductURL, SimpleProduct, httpClient, transformer ),
|
||||
restRead< SimpleProductRepositoryParams >( buildProductURL, SimpleProduct, httpClient, transformer ),
|
||||
restUpdate< SimpleProductRepositoryParams >( buildProductURL, SimpleProduct, httpClient, transformer ),
|
||||
restDelete< SimpleProductRepositoryParams >( buildProductURL, httpClient ),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -17,6 +17,36 @@
|
|||
},
|
||||
"variable": {
|
||||
"name": "Variable Product with Three Variations"
|
||||
},
|
||||
"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": {
|
||||
|
|
|
@ -16,7 +16,7 @@ This package contains the automated end-to-end tests for WooCommerce.
|
|||
|
||||
### Setting up the test environment
|
||||
|
||||
Follow [E2E setup instructions](https://github.com/woocommerce/woocommerce/blob/master/tests/e2e/README.md).
|
||||
Follow [E2E setup instructions](https://github.com/woocommerce/woocommerce/blob/trunk/tests/e2e/README.md).
|
||||
|
||||
### Setting up core tests
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"name": "@woocommerce/e2e-core-tests",
|
||||
"version": "0.1.1",
|
||||
"description": "End-To-End (E2E) tests for WooCommerce",
|
||||
"homepage": "https://github.com/woocommerce/woocommerce/tree/master/tests/e2e/core-tests/README.md",
|
||||
"homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/tests/e2e/core-tests/README.md",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/woocommerce/woocommerce.git"
|
||||
|
@ -14,6 +14,7 @@
|
|||
"config": "3.3.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@woocommerce/api": "^0.1.1",
|
||||
"@woocommerce/e2e-utils": "^0.1.2"
|
||||
},
|
||||
"publishConfig": {
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
/* eslint-disable jest/no-export, jest/no-disabled-tests */
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
const { HTTPClientFactory, Coupon } = require( '@woocommerce/api' );
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
const config = require( 'config' );
|
||||
const {
|
||||
it,
|
||||
describe,
|
||||
beforeAll,
|
||||
} = require( '@jest/globals' );
|
||||
|
||||
/**
|
||||
* Create the default coupon and tests interactions with it via the API.
|
||||
*/
|
||||
const runCouponApiTest = () => {
|
||||
describe('REST API > Coupon', () => {
|
||||
let client;
|
||||
let percentageCoupon;
|
||||
let coupon;
|
||||
let repository;
|
||||
|
||||
beforeAll(async () => {
|
||||
percentageCoupon = config.get( 'coupons.percentage' );
|
||||
const admin = config.get( 'users.admin' );
|
||||
const url = config.get( 'url' );
|
||||
|
||||
client = HTTPClientFactory.build( url )
|
||||
.withBasicAuth( admin.username, admin.password )
|
||||
.withIndexPermalinks()
|
||||
.create();
|
||||
} );
|
||||
|
||||
it('can create a coupon', async () => {
|
||||
repository = Coupon.restRepository( client );
|
||||
|
||||
// Check properties of the coupon in the create coupon response.
|
||||
coupon = await repository.create( percentageCoupon );
|
||||
expect( coupon ).toEqual( expect.objectContaining( percentageCoupon ) );
|
||||
});
|
||||
|
||||
it('can retrieve a coupon', async () => {
|
||||
const couponProperties = {
|
||||
id: coupon.id,
|
||||
code: percentageCoupon.code,
|
||||
discount_type: percentageCoupon.discountType,
|
||||
amount: percentageCoupon.amount,
|
||||
};
|
||||
|
||||
// Read coupon directly from API to compare.
|
||||
const response = await client.get( `/wc/v3/coupons/${coupon.id}` );
|
||||
expect( response.statusCode ).toBe( 200 );
|
||||
expect( response.data ).toEqual( expect.objectContaining( couponProperties ) );
|
||||
});
|
||||
|
||||
it('can update a coupon', async () => {
|
||||
const updatedCouponProperties = {
|
||||
amount: '75.00',
|
||||
discount_type: 'fixed_cart',
|
||||
free_shipping: true,
|
||||
};
|
||||
|
||||
await repository.update( coupon.id, updatedCouponProperties );
|
||||
|
||||
// Check the coupon response for the updated values.
|
||||
const response = await client.get( `/wc/v3/coupons/${coupon.id}` );
|
||||
expect( response.statusCode ).toBe( 200 );
|
||||
expect( response.data ).toEqual( expect.objectContaining( updatedCouponProperties ) );
|
||||
});
|
||||
|
||||
it('can delete a coupon', async () => {
|
||||
// Delete the coupon
|
||||
const deletedCoupon = await repository.delete( coupon.id );
|
||||
|
||||
// If the delete is successful, the response comes back truthy
|
||||
expect( deletedCoupon ).toBeTruthy();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = runCouponApiTest;
|
|
@ -0,0 +1,75 @@
|
|||
/* eslint-disable jest/no-export, jest/no-disabled-tests */
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
const { HTTPClientFactory, ExternalProduct } = require( '@woocommerce/api' );
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
const config = require( 'config' );
|
||||
const {
|
||||
it,
|
||||
describe,
|
||||
beforeAll,
|
||||
} = require( '@jest/globals' );
|
||||
|
||||
/**
|
||||
* Create an external product and retrieve via the API.
|
||||
*/
|
||||
const runExternalProductAPITest = () => {
|
||||
// @todo: add a call to ensure pretty permalinks are enabled once settings api is in use.
|
||||
describe('REST API > External Product', () => {
|
||||
let client;
|
||||
let defaultExternalProduct;
|
||||
let product;
|
||||
let repository;
|
||||
|
||||
beforeAll(async () => {
|
||||
defaultExternalProduct = config.get( 'products.external' );
|
||||
const admin = config.get( 'users.admin' );
|
||||
const url = config.get( 'url' );
|
||||
|
||||
client = HTTPClientFactory.build( url )
|
||||
.withBasicAuth( admin.username, admin.password )
|
||||
.withIndexPermalinks()
|
||||
.create();
|
||||
} );
|
||||
|
||||
it('can create an external product', async () => {
|
||||
repository = ExternalProduct.restRepository( client );
|
||||
|
||||
// Check properties of product in the create product response.
|
||||
product = await repository.create( defaultExternalProduct );
|
||||
expect( product ).toEqual( expect.objectContaining( defaultExternalProduct ) );
|
||||
});
|
||||
|
||||
it('can retrieve a raw external product', async () => {
|
||||
const rawProperties = {
|
||||
id: product.id,
|
||||
button_text: defaultExternalProduct.buttonText,
|
||||
external_url: defaultExternalProduct.externalUrl,
|
||||
price: defaultExternalProduct.regularPrice,
|
||||
};
|
||||
|
||||
// Read product directly from api.
|
||||
const response = await client.get( `/wc/v3/products/${product.id}` );
|
||||
expect( response.statusCode ).toBe( 200 );
|
||||
expect( response.data ).toEqual( expect.objectContaining( rawProperties ) );
|
||||
});
|
||||
|
||||
it('can retrieve a transformed external product', async () => {
|
||||
const transformedProperties = {
|
||||
...defaultExternalProduct,
|
||||
id: product.id,
|
||||
price: defaultExternalProduct.regularPrice,
|
||||
};
|
||||
|
||||
// Read product via the repository.
|
||||
const transformed = await repository.read( product.id );
|
||||
expect( transformed ).toEqual( expect.objectContaining( transformedProperties ) );
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = runExternalProductAPITest;
|
|
@ -0,0 +1,82 @@
|
|||
/* eslint-disable jest/no-export, jest/no-disabled-tests */
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
const { HTTPClientFactory, GroupedProduct, SimpleProduct } = require( '@woocommerce/api' );
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
const config = require( 'config' );
|
||||
const {
|
||||
it,
|
||||
describe,
|
||||
beforeAll,
|
||||
} = require( '@jest/globals' );
|
||||
|
||||
/**
|
||||
* Create an external product and retrieve via the API.
|
||||
*/
|
||||
const runGroupedProductAPITest = () => {
|
||||
// @todo: add a call to ensure pretty permalinks are enabled once settings api is in use.
|
||||
describe('REST API > Grouped Product', () => {
|
||||
let client;
|
||||
let defaultGroupedProduct;
|
||||
let baseGroupedProduct;
|
||||
let product;
|
||||
let groupedProducts = [];
|
||||
let repository;
|
||||
|
||||
beforeAll(async () => {
|
||||
defaultGroupedProduct = config.get( 'products.grouped' );
|
||||
const admin = config.get( 'users.admin' );
|
||||
const url = config.get( 'url' );
|
||||
|
||||
client = HTTPClientFactory.build( url )
|
||||
.withBasicAuth( admin.username, admin.password )
|
||||
.withIndexPermalinks()
|
||||
.create();
|
||||
|
||||
// Create the simple products to be grouped first.
|
||||
repository = SimpleProduct.restRepository( client );
|
||||
for ( let c = 0; c < defaultGroupedProduct.groupedProducts.length; c++ ) {
|
||||
product = await repository.create( defaultGroupedProduct.groupedProducts[ c ] );
|
||||
groupedProducts.push( product.id );
|
||||
}
|
||||
});
|
||||
|
||||
it('can create a grouped product', async () => {
|
||||
baseGroupedProduct = {
|
||||
...defaultGroupedProduct,
|
||||
groupedProducts,
|
||||
};
|
||||
repository = GroupedProduct.restRepository( client );
|
||||
|
||||
// Check properties of product in the create product response.
|
||||
product = await repository.create( baseGroupedProduct );
|
||||
expect( product ).toEqual( expect.objectContaining( baseGroupedProduct ) );
|
||||
});
|
||||
|
||||
it('can retrieve a raw external product', async () => {
|
||||
let rawProperties = {
|
||||
id: product.id,
|
||||
grouped_products: baseGroupedProduct.groupedProducts,
|
||||
...defaultGroupedProduct,
|
||||
};
|
||||
delete rawProperties['groupedProducts'];
|
||||
|
||||
// Read product directly from api.
|
||||
const response = await client.get( `/wc/v3/products/${product.id}` );
|
||||
expect( response.statusCode ).toBe( 200 );
|
||||
expect( response.data ).toEqual( expect.objectContaining( rawProperties ) );
|
||||
});
|
||||
|
||||
it('can retrieve a transformed external product', async () => {
|
||||
// Read product via the repository.
|
||||
const transformed = await repository.read( product.id );
|
||||
expect( transformed ).toEqual( expect.objectContaining( baseGroupedProduct ) );
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = runGroupedProductAPITest;
|
|
@ -20,6 +20,7 @@ const runVariableProductUpdateTest = require( './shopper/front-end-variable-prod
|
|||
// Merchant tests
|
||||
const runCreateCouponTest = require( './merchant/wp-admin-coupon-new.test' );
|
||||
const runCreateOrderTest = require( './merchant/wp-admin-order-new.test' );
|
||||
const runEditOrderTest = require( './merchant/wp-admin-order-edit.test' );
|
||||
const { runAddSimpleProductTest, runAddVariableProductTest } = require( './merchant/wp-admin-product-new.test' );
|
||||
const runUpdateGeneralSettingsTest = require( './merchant/wp-admin-settings-general.test' );
|
||||
const runProductSettingsTest = require( './merchant/wp-admin-settings-product.test' );
|
||||
|
@ -31,6 +32,11 @@ const runProductEditDetailsTest = require( './merchant/wp-admin-product-edit-det
|
|||
const runProductSearchTest = require( './merchant/wp-admin-product-search.test' );
|
||||
const runMerchantOrdersCustomerPaymentPage = require( './merchant/wp-admin-order-customer-payment-page.test' );
|
||||
|
||||
// REST API tests
|
||||
const runExternalProductAPITest = require( './api/external-product.test' );
|
||||
const runCouponApiTest = require( './api/coupon.test' );
|
||||
const runGroupedProductAPITest = require( './api/grouped-product.test' );
|
||||
|
||||
const runSetupOnboardingTests = () => {
|
||||
runActivationTest();
|
||||
runOnboardingFlowTest();
|
||||
|
@ -51,6 +57,7 @@ const runShopperTests = () => {
|
|||
const runMerchantTests = () => {
|
||||
runCreateCouponTest();
|
||||
runCreateOrderTest();
|
||||
runEditOrderTest();
|
||||
runAddSimpleProductTest();
|
||||
runAddVariableProductTest();
|
||||
runUpdateGeneralSettingsTest();
|
||||
|
@ -64,12 +71,20 @@ const runMerchantTests = () => {
|
|||
runMerchantOrdersCustomerPaymentPage();
|
||||
}
|
||||
|
||||
const runApiTests = () => {
|
||||
runExternalProductAPITest();
|
||||
runCouponApiTest();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
runActivationTest,
|
||||
runOnboardingFlowTest,
|
||||
runTaskListTest,
|
||||
runInitialStoreSettingsTest,
|
||||
runSetupOnboardingTests,
|
||||
runExternalProductAPITest,
|
||||
runGroupedProductAPITest,
|
||||
runCouponApiTest,
|
||||
runCartApplyCouponsTest,
|
||||
runCartPageTest,
|
||||
runCheckoutApplyCouponsTest,
|
||||
|
@ -80,6 +95,7 @@ module.exports = {
|
|||
runShopperTests,
|
||||
runCreateCouponTest,
|
||||
runCreateOrderTest,
|
||||
runEditOrderTest,
|
||||
runAddSimpleProductTest,
|
||||
runAddVariableProductTest,
|
||||
runUpdateGeneralSettingsTest,
|
||||
|
@ -92,4 +108,5 @@ module.exports = {
|
|||
runProductSearchTest,
|
||||
runMerchantOrdersCustomerPaymentPage,
|
||||
runMerchantTests,
|
||||
runApiTests,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
/* eslint-disable jest/no-export, jest/no-disabled-tests */
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
const {
|
||||
merchant,
|
||||
createSimpleOrder,
|
||||
moveAllItemsToTrash
|
||||
} = require( '@woocommerce/e2e-utils' );
|
||||
|
||||
let orderId;
|
||||
|
||||
const runEditOrderTest = () => {
|
||||
describe('WooCommerce Orders > Edit order', () => {
|
||||
beforeAll(async () => {
|
||||
await merchant.login();
|
||||
orderId = await createSimpleOrder('Processing');
|
||||
});
|
||||
|
||||
afterAll( async () => {
|
||||
// Make sure we're on the all orders view and cleanup the orders we created
|
||||
await merchant.openAllOrdersView();
|
||||
await moveAllItemsToTrash();
|
||||
});
|
||||
|
||||
it('can view single order', async () => {
|
||||
// Go to "orders" page
|
||||
await merchant.openAllOrdersView();
|
||||
|
||||
// Make sure we're on the orders page
|
||||
await expect(page.title()).resolves.toMatch('Orders');
|
||||
|
||||
//Open order we created
|
||||
await merchant.goToOrder(orderId);
|
||||
|
||||
// Make sure we're on the order details page
|
||||
await expect(page.title()).resolves.toMatch('Edit order');
|
||||
});
|
||||
|
||||
it('can update order status', async () => {
|
||||
//Open order we created
|
||||
await merchant.goToOrder(orderId);
|
||||
|
||||
// Make sure we're still on the order details page
|
||||
await expect(page.title()).resolves.toMatch('Edit order');
|
||||
|
||||
// Update order status to `Completed`
|
||||
await merchant.updateOrderStatus(orderId, 'Completed');
|
||||
|
||||
// Verify order status changed note added
|
||||
await expect( page ).toMatchElement( '#select2-order_status-container', { text: 'Completed' } );
|
||||
await expect( page ).toMatchElement(
|
||||
'#woocommerce-order-notes .note_content',
|
||||
{
|
||||
text: 'Order status changed from Processing to Completed.',
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('can update order details', async () => {
|
||||
//Open order we created
|
||||
await merchant.goToOrder(orderId);
|
||||
|
||||
// Make sure we're still on the order details page
|
||||
await expect(page.title()).resolves.toMatch('Edit order');
|
||||
|
||||
// Update order details
|
||||
await expect(page).toFill('input[name=order_date]', '2018-12-14');
|
||||
|
||||
// Wait for auto save
|
||||
await page.waitFor( 2000 );
|
||||
|
||||
// Save the order changes
|
||||
await expect( page ).toClick( 'button.save_order' );
|
||||
await page.waitForSelector( '#message' );
|
||||
|
||||
// Verify
|
||||
await expect( page ).toMatchElement( '#message', { text: 'Order updated.' } );
|
||||
await expect( page ).toMatchElement( 'input[name=order_date]', { value: '2018-12-14' } );
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = runEditOrderTest;
|
|
@ -8,7 +8,7 @@ const {
|
|||
} = require( '@woocommerce/e2e-utils' );
|
||||
|
||||
const runCreateOrderTest = () => {
|
||||
describe('Add New Order Page', () => {
|
||||
describe('WooCommerce Orders > Add new order', () => {
|
||||
beforeAll(async () => {
|
||||
await merchant.login();
|
||||
});
|
||||
|
|
|
@ -121,9 +121,9 @@ Jest provides setup and teardown functions similar to PHPUnit. The default setup
|
|||
|
||||
Depending on the project and testing scenario, the built in testing environment container might not be the best solution for testing. This could be local testing where there is already a testing container, a repository that isn't a plugin or theme and there are multiple folders mapped into the container, or similar. The `e2e-environment` container supports using either the built in container or an external container. See the the appropriate readme for details:
|
||||
|
||||
- [Built In Container](https://github.com/woocommerce/woocommerce/tree/master/tests/e2e/env/builtin.md)
|
||||
- [External Container](https://github.com/woocommerce/woocommerce/tree/master/tests/e2e/env/external.md)
|
||||
- [Built In Container](https://github.com/woocommerce/woocommerce/tree/trunk/tests/e2e/env/builtin.md)
|
||||
- [External Container](https://github.com/woocommerce/woocommerce/tree/trunk/tests/e2e/env/external.md)
|
||||
|
||||
## Additional information
|
||||
|
||||
Refer to [`tests/e2e/core-tests`](https://github.com/woocommerce/woocommerce/tree/master/tests/e2e/core-tests) for some test examples, and [`tests/e2e`](https://github.com/woocommerce/woocommerce/tree/master/tests/e2e) for general information on e2e tests.
|
||||
Refer to [`tests/e2e/core-tests`](https://github.com/woocommerce/woocommerce/tree/trunk/tests/e2e/core-tests) for some test examples, and [`tests/e2e`](https://github.com/woocommerce/woocommerce/tree/trunk/tests/e2e) for general information on e2e tests.
|
||||
|
|
|
@ -16,7 +16,7 @@ wp post create --post_type=page --post_status=publish --post_title='Ready' --pos
|
|||
|
||||
### Project Initialization
|
||||
|
||||
Each project will have its own begin test state and initialization script. For example, a project might start testing expecting that the [sample products](https://github.com/woocommerce/woocommerce/tree/master/sample-data) have already been imported. Below is the WP CLI equivalent of the built in initialization script for WooCommerce Core E2E testing:
|
||||
Each project will have its own begin test state and initialization script. For example, a project might start testing expecting that the [sample products](https://github.com/woocommerce/woocommerce/tree/trunk/sample-data) have already been imported. Below is the WP CLI equivalent of the built in initialization script for WooCommerce Core E2E testing:
|
||||
|
||||
|
||||
```
|
||||
|
|
|
@ -16,7 +16,7 @@ wp post create --post_type=page --post_status=publish --post_title='Ready' --pos
|
|||
|
||||
### Project Initialization
|
||||
|
||||
Each project will have its own begin test state and initialization script. For example, a project might start testing expecting that the [sample products](https://github.com/woocommerce/woocommerce/tree/master/sample-data) have already been imported. Below is the WP CLI equivalent initialization script for WooCommerce Core E2E testing:
|
||||
Each project will have its own begin test state and initialization script. For example, a project might start testing expecting that the [sample products](https://github.com/woocommerce/woocommerce/tree/trunk/sample-data) have already been imported. Below is the WP CLI equivalent initialization script for WooCommerce Core E2E testing:
|
||||
|
||||
|
||||
```
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
"e2e",
|
||||
"puppeteer"
|
||||
],
|
||||
"homepage": "https://github.com/woocommerce/woocommerce/tree/master/tests/e2e/env/README.md",
|
||||
"homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/tests/e2e/env/README.md",
|
||||
"bugs": {
|
||||
"url": "https://github.com/woocommerce/woocommerce/issues"
|
||||
},
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
/*
|
||||
* Internal dependencies
|
||||
*/
|
||||
const { runApiTests } = require( '@woocommerce/e2e-core-tests' );
|
||||
|
||||
runApiTests();
|
|
@ -0,0 +1,6 @@
|
|||
/*
|
||||
* Internal dependencies
|
||||
*/
|
||||
const { runCouponApiTest } = require( '@woocommerce/e2e-core-tests' );
|
||||
|
||||
runCouponApiTest();
|
|
@ -0,0 +1,6 @@
|
|||
/*
|
||||
* Internal dependencies
|
||||
*/
|
||||
const { runExternalProductAPITest } = require( '@woocommerce/e2e-core-tests' );
|
||||
|
||||
runExternalProductAPITest();
|
|
@ -0,0 +1,6 @@
|
|||
/*
|
||||
* Internal dependencies
|
||||
*/
|
||||
const { runGroupedProductAPITest } = require( '@woocommerce/e2e-core-tests' );
|
||||
|
||||
runGroupedProductAPITest();
|
|
@ -0,0 +1,6 @@
|
|||
/*
|
||||
* Internal dependencies
|
||||
*/
|
||||
const { runEditOrderTest } = require( '@woocommerce/e2e-core-tests' );
|
||||
|
||||
runEditOrderTest();
|
|
@ -2,7 +2,7 @@
|
|||
"name": "@woocommerce/e2e-utils",
|
||||
"version": "0.1.2",
|
||||
"description": "End-To-End (E2E) test utils for WooCommerce",
|
||||
"homepage": "https://github.com/woocommerce/woocommerce/tree/master/tests/e2e-utils/README.md",
|
||||
"homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/tests/e2e-utils/README.md",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/woocommerce/woocommerce.git"
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
<?php
|
||||
/**
|
||||
* Class WC_Tests_Admin_Dashboard file.
|
||||
*
|
||||
* @package WooCommerce\Tests\Admin
|
||||
*/
|
||||
|
||||
/**
|
||||
* Tests for the WC_Admin_Report class.
|
||||
*/
|
||||
class WC_Tests_Admin_Dashboard extends WC_Unit_Test_Case {
|
||||
|
||||
/**
|
||||
* Set up for tests.
|
||||
*/
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
$this->user = $this->factory->user->create(
|
||||
array(
|
||||
'role' => 'administrator',
|
||||
)
|
||||
);
|
||||
|
||||
// Mock http request to performance endpoint.
|
||||
add_filter( 'rest_pre_dispatch', array( $this, 'mock_rest_responses' ), 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tear down.
|
||||
*/
|
||||
public function tearDown() {
|
||||
parent::tearDown();
|
||||
remove_filter( 'rest_pre_dispatch', array( $this, 'mock_rest_responses' ), 10 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: get_status_widget
|
||||
*/
|
||||
public function test_status_widget() {
|
||||
wp_set_current_user( $this->user );
|
||||
$order = WC_Helper_Order::create_order();
|
||||
$order->set_status( 'completed' );
|
||||
$order->save();
|
||||
|
||||
$this->expectOutputRegex( '/98,765\.00/' );
|
||||
|
||||
( new WC_Admin_Dashboard() )->status_widget();
|
||||
|
||||
$widget_output = $this->getActualOutput();
|
||||
|
||||
$this->assertRegExp( '/page\=wc-admin\&\#038\;path\=\%2Fanalytics\%2Frevenue/', $widget_output );
|
||||
$this->assertRegExp( '/page\=wc-admin\&\#038\;filter\=single_product/', $widget_output );
|
||||
$this->assertRegExp( '/page\=wc-admin\&\#038\;type\=lowstock/', $widget_output );
|
||||
$this->assertRegExp( '/page\=wc-admin\&\#038\;type\=outofstock/', $widget_output );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: get_status_widget with woo admin disabled.
|
||||
*/
|
||||
public function test_status_widget_with_woo_admin_disabled() {
|
||||
wp_set_current_user( $this->user );
|
||||
$order = WC_Helper_Order::create_order();
|
||||
$order->set_status( 'completed' );
|
||||
$order->save();
|
||||
|
||||
add_filter( 'woocommerce_admin_disabled', '__return_true' );
|
||||
|
||||
$this->expectOutputRegex( '/50\.00 worth in the/' );
|
||||
|
||||
( new WC_Admin_Dashboard() )->status_widget();
|
||||
|
||||
$widget_output = $this->getActualOutput();
|
||||
$this->assertRegExp( '/page\=wc-reports\&\#038\;tab\=orders\&\#038\;range\=month/', $widget_output );
|
||||
$this->assertRegExp( '/page\=wc-reports\&\#038\;tab\=orders\&\#038\;report\=sales_by_product/', $widget_output );
|
||||
$this->assertRegExp( '/page\=wc-reports\&\#038\;tab\=stock\&\#038\;report\=low_in_stock/', $widget_output );
|
||||
$this->assertRegExp( '/page\=wc-reports\&\#038\;tab\=stock\&\#038\;report\=out_of_stock/', $widget_output );
|
||||
|
||||
remove_filter( 'woocommerce_admin_disabled', '__return_true' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to mock rest_do_request method.
|
||||
*
|
||||
* @param false $response Request arguments.
|
||||
* @param WP_REST_Server $rest_server rest server class.
|
||||
* @param WP_REST_Request $request incoming request.
|
||||
*
|
||||
* @return WP_REST_Response|false mocked response or false to let WP perform a regular request.
|
||||
*/
|
||||
public function mock_rest_responses( $response, $rest_server, $request ) {
|
||||
if ( '/wc-analytics/reports/performance-indicators' === $request->get_route() ) {
|
||||
$response = new WP_REST_Response(
|
||||
array(
|
||||
'status' => 200,
|
||||
)
|
||||
);
|
||||
$response->set_data(
|
||||
array(
|
||||
array(
|
||||
'chart' => 'net_revenue',
|
||||
'value' => 98765.0,
|
||||
),
|
||||
)
|
||||
);
|
||||
} elseif ( '/wc-analytics/reports/revenue/stats' === $request->get_route() ) {
|
||||
$response = new WP_REST_Response(
|
||||
array(
|
||||
'status' => 200,
|
||||
)
|
||||
);
|
||||
$response->set_data(
|
||||
array(
|
||||
'intervals' => array(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
|
@ -46,7 +46,7 @@ class WC_Cart_Test extends \WC_Unit_Test_Case {
|
|||
);
|
||||
$notices = WC()->session->get( 'wc_notices', array() );
|
||||
|
||||
// Check that the second add to cart call increases the quantity of the existing cart-item.
|
||||
// Check for cart contents.
|
||||
$this->assertCount( 0, WC()->cart->get_cart_contents() );
|
||||
$this->assertEquals( 0, WC()->cart->get_cart_contents_count() );
|
||||
|
||||
|
@ -61,6 +61,41 @@ class WC_Cart_Test extends \WC_Unit_Test_Case {
|
|||
$product->delete( true );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox should throw a notice to the cart if using variation_id
|
||||
* that doesn't belong to specified variable product.
|
||||
*/
|
||||
public function test_add_variation_to_the_cart_invalid_variation_id() {
|
||||
WC()->cart->empty_cart();
|
||||
WC()->session->set( 'wc_notices', null );
|
||||
|
||||
$variable_product = WC_Helper_Product::create_variation_product();
|
||||
$single_product = WC_Helper_Product::create_simple_product();
|
||||
|
||||
// Add variation using parent id.
|
||||
WC()->cart->add_to_cart(
|
||||
$variable_product->get_id(),
|
||||
1,
|
||||
$single_product->get_id()
|
||||
);
|
||||
$notices = WC()->session->get( 'wc_notices', array() );
|
||||
|
||||
// Check for cart contents.
|
||||
$this->assertCount( 0, WC()->cart->get_cart_contents() );
|
||||
$this->assertEquals( 0, WC()->cart->get_cart_contents_count() );
|
||||
|
||||
// Check that the notices contain an error message about invalid colour and number.
|
||||
$this->assertArrayHasKey( 'error', $notices );
|
||||
$this->assertCount( 1, $notices['error'] );
|
||||
$expected = sprintf( sprintf( 'The selected product isn\'t a variation of %2$s, please choose product options by visiting <a href="%1$s" title="%2$s">%2$s</a>.', esc_url( $variable_product->get_permalink() ), esc_html( $variable_product->get_name() ) ) );
|
||||
$this->assertEquals( $expected, $notices['error'][0]['notice'] );
|
||||
|
||||
// Reset cart.
|
||||
WC()->cart->empty_cart();
|
||||
WC()->customer->set_is_vat_exempt( false );
|
||||
$variable_product->delete( true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test show shipping.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
/**
|
||||
* Unit tests for the WC_Tracker class.
|
||||
*
|
||||
* @package WooCommerce\Tests\WC_Tracker.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WC_Tracker_Test
|
||||
*/
|
||||
class WC_Tracker_Test extends \WC_Unit_Test_Case {
|
||||
/**
|
||||
* Test the tracking of wc_admin being disabled via filter.
|
||||
*/
|
||||
public function test_wc_admin_disabled_get_tracking_data() {
|
||||
$posted_data = null;
|
||||
|
||||
// Test the case for woocommerce_admin_disabled filter returning true.
|
||||
add_filter(
|
||||
'woocommerce_admin_disabled',
|
||||
function( $default ) {
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
add_filter(
|
||||
'pre_http_request',
|
||||
function( $pre, $args, $url ) use ( &$posted_data ) {
|
||||
$posted_data = $args;
|
||||
return true;
|
||||
},
|
||||
3,
|
||||
10
|
||||
);
|
||||
WC_Tracker::send_tracking_data( true );
|
||||
$tracking_data = json_decode( $posted_data['body'], true );
|
||||
|
||||
// Test the default case of no filter for set for woocommerce_admin_disabled.
|
||||
$this->assertArrayHasKey( 'wc_admin_disabled', $tracking_data );
|
||||
$this->assertEquals( 'yes', $tracking_data['wc_admin_disabled'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the tracking of wc_admin being not disabled via filter.
|
||||
*/
|
||||
public function test_wc_admin_not_disabled_get_tracking_data() {
|
||||
$posted_data = null;
|
||||
// Bypass time delay so we can invoke send_tracking_data again.
|
||||
update_option( 'woocommerce_tracker_last_send', strtotime( '-2 weeks' ) );
|
||||
|
||||
add_filter(
|
||||
'pre_http_request',
|
||||
function( $pre, $args, $url ) use ( &$posted_data ) {
|
||||
$posted_data = $args;
|
||||
return true;
|
||||
},
|
||||
3,
|
||||
10
|
||||
);
|
||||
WC_Tracker::send_tracking_data( true );
|
||||
$tracking_data = json_decode( $posted_data['body'], true );
|
||||
|
||||
// Test the default case of no filter for set for woocommerce_admin_disabled.
|
||||
$this->assertArrayHasKey( 'wc_admin_disabled', $tracking_data );
|
||||
$this->assertEquals( 'no', $tracking_data['wc_admin_disabled'] );
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
* Plugin Name: WooCommerce
|
||||
* Plugin URI: https://woocommerce.com/
|
||||
* Description: An eCommerce toolkit that helps you sell anything. Beautifully.
|
||||
* Version: 5.1.0-dev
|
||||
* Version: 5.2.0-dev
|
||||
* Author: Automattic
|
||||
* Author URI: https://woocommerce.com
|
||||
* Text Domain: woocommerce
|
||||
|
|
Loading…
Reference in New Issue