Merge branch 'trunk' into refactor/settings-pages-classes-take-2
This commit is contained in:
commit
8b12fee96f
|
@ -0,0 +1,30 @@
|
|||
name: Build release zip file
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ref:
|
||||
description: 'By default the zip file is generated from the branch the workflow runs from, but you can specify an explicit reference to use instead here (e.g. refs/tags/tag_name). The resulting file will be available as an artifact on the workflow run.'
|
||||
required: false
|
||||
default: ''
|
||||
jobs:
|
||||
build:
|
||||
name: Build release zip file
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.event.inputs.ref || github.ref }}
|
||||
- name: Build the zip file
|
||||
id: build
|
||||
uses: woocommerce/action-build@v2
|
||||
- name: Unzip the file (prevents double zip problem)
|
||||
run: unzip ${{ steps.build.outputs.zip_path }} -d zipfile
|
||||
- name: Upload the zip file as an artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
name: woocommerce
|
||||
path: zipfile
|
||||
retention-days: 7
|
|
@ -0,0 +1,32 @@
|
|||
name: "Pull request post-merge processing"
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
|
||||
jobs:
|
||||
assign-milestone:
|
||||
name: "Assign milestone to merged pull request"
|
||||
if: github.event.pull_request.merged == true && ! github.event.pull_request.milestone
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: "Get the milestone changer script"
|
||||
run: |
|
||||
curl \
|
||||
--silent \
|
||||
--fail \
|
||||
--header 'Authorization: bearer ${{ secrets.GITHUB_TOKEN }}' \
|
||||
--header 'User-Agent: GitHub action to set the milestone for a pull request' \
|
||||
--header 'Accept: application/vnd.github.v3.raw' \
|
||||
--remote-name \
|
||||
--location $GITHUB_API_URL/repos/${{ github.repository }}/contents/.github/workflows/scripts/assign-milestone-to-merged-pr.php
|
||||
env:
|
||||
GITHUB_API_URL: ${{ env.GITHUB_API_URL }}
|
||||
- name: "Install PHP"
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '7.4'
|
||||
- name: "Run the milestone changer script"
|
||||
run: php assign-milestone-to-merged-pr.php
|
||||
env:
|
||||
PULL_REQUEST_ID: ${{ github.event.pull_request.node_id }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@ -0,0 +1,144 @@
|
|||
<?php
|
||||
/**
|
||||
* Script to automatically assign a milestone to a pull request when it's merged.
|
||||
*
|
||||
* @package WooCommerce/GithubActions
|
||||
*/
|
||||
|
||||
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped, WordPress.WP.AlternativeFunctions
|
||||
|
||||
global $repo_owner, $repo_name, $github_token, $graphql_api_url;
|
||||
|
||||
/**
|
||||
* Grab/process input.
|
||||
*/
|
||||
|
||||
$repo_parts = explode( '/', getenv( 'GITHUB_REPOSITORY' ) );
|
||||
$repo_owner = $repo_parts[0];
|
||||
$repo_name = $repo_parts[1];
|
||||
|
||||
$pr_id = getenv( 'PULL_REQUEST_ID' );
|
||||
$github_token = getenv( 'GITHUB_TOKEN' );
|
||||
$graphql_api_url = getenv( 'GITHUB_GRAPHQL_URL' );
|
||||
|
||||
/**
|
||||
* Select the milestone to be added:
|
||||
*
|
||||
* 1. Get the first 10 milestones sorted by creation date descending.
|
||||
* (we'll never have more than 2 or 3 active milestones but let's get 10 to be sure).
|
||||
* 2. Discard those not open or whose title is not a proper version number ("X.Y.Z").
|
||||
* 3. Sort descending using version_compare.
|
||||
* 4. Get the oldest one that does not have a corresponding "release/X.Y" branch.
|
||||
*/
|
||||
|
||||
echo "Getting the list of milestones...\n";
|
||||
|
||||
$query = "
|
||||
repository(owner:\"$repo_owner\", name:\"$repo_name\") {
|
||||
milestones(first: 10, states: [OPEN], orderBy: {field: CREATED_AT, direction: DESC}) {
|
||||
nodes {
|
||||
id
|
||||
title
|
||||
state
|
||||
}
|
||||
}
|
||||
}
|
||||
";
|
||||
$json = do_graphql_api_request( $query );
|
||||
$milestones = $json['data']['repository']['milestones']['nodes'];
|
||||
$milestones = array_map(
|
||||
function( $x ) {
|
||||
return 1 === preg_match( '/^\d+\.\d+\.\d+$/D', $x['title'] ) ? $x : null;
|
||||
},
|
||||
$milestones
|
||||
);
|
||||
$milestones = array_filter( $milestones );
|
||||
usort(
|
||||
$milestones,
|
||||
function( $a, $b ) {
|
||||
return version_compare( $b['title'], $a['title'] );
|
||||
}
|
||||
);
|
||||
|
||||
echo 'Latest open milestone: ' . $milestones[0]['title'] . "\n";
|
||||
|
||||
$chosen_milestone = null;
|
||||
foreach ( $milestones as $milestone ) {
|
||||
$milestone_title_parts = explode( '.', $milestone['title'] );
|
||||
$milestone_release_branch = 'release/' . $milestone_title_parts[0] . '.' . $milestone_title_parts[1];
|
||||
|
||||
$query = "
|
||||
repository(owner:\"$repo_owner\", name:\"$repo_name\") {
|
||||
ref(qualifiedName: \"refs/heads/$milestone_release_branch\") {
|
||||
id
|
||||
}
|
||||
}
|
||||
";
|
||||
$result = do_graphql_api_request( $query );
|
||||
|
||||
if ( is_null( $result['data']['repository']['ref'] ) ) {
|
||||
$chosen_milestone = $milestone;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If all the milestones have a release branch, just take the newest one.
|
||||
if ( is_null( $chosen_milestone ) ) {
|
||||
$chosen_milestone = $milestones[0];
|
||||
}
|
||||
|
||||
echo 'Milestone that will be assigned: ' . $chosen_milestone['title'] . "\n";
|
||||
|
||||
if ( getenv( 'DRY_RUN' ) ) {
|
||||
echo "Dry run, skipping the actual milestone assignment\n";
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Assign the milestone to the pull request.
|
||||
*/
|
||||
|
||||
echo "Assigning the milestone to the pull request...\n";
|
||||
|
||||
$milestone_id = $chosen_milestone['id'];
|
||||
$mutation = "
|
||||
updatePullRequest(input: {pullRequestId: \"$pr_id\", milestoneId: \"$milestone_id\"}) {
|
||||
clientMutationId
|
||||
}
|
||||
";
|
||||
|
||||
do_graphql_api_request( $mutation, true );
|
||||
|
||||
/**
|
||||
* Function to query the GitHub GraphQL API.
|
||||
*
|
||||
* @param string $body The GraphQL-formatted request body, without "query" or "mutation" wrapper.
|
||||
* @param bool $is_mutation True if the request is a mutation, false if it's a query.
|
||||
* @return array The json-decoded response.
|
||||
*/
|
||||
function do_graphql_api_request( $body, $is_mutation = false ) {
|
||||
global $github_token, $graphql_api_url;
|
||||
|
||||
$keyword = $is_mutation ? 'mutation' : 'query';
|
||||
$data = array( 'query' => "$keyword { $body }" );
|
||||
$context = stream_context_create(
|
||||
array(
|
||||
'http' => array(
|
||||
'method' => 'POST',
|
||||
'header' => array(
|
||||
'Accept: application/json',
|
||||
'Content-Type: application/json',
|
||||
'User-Agent: GitHub action to set the milestone for a pull request',
|
||||
'Authorization: bearer ' . $github_token,
|
||||
),
|
||||
'content' => json_encode( $data ),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
$result = file_get_contents( $graphql_api_url, false, $context );
|
||||
return json_decode( $result, true );
|
||||
}
|
||||
|
||||
// phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped, WordPress.WP.AlternativeFunctions
|
|
@ -4,7 +4,7 @@ on:
|
|||
- cron: '25 3 * * *'
|
||||
jobs:
|
||||
login-run:
|
||||
name: Log into smoke test site.
|
||||
name: Daily smoke test on trunk.
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
|
||||
|
@ -18,21 +18,26 @@ jobs:
|
|||
- name: Checkout code.
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: package/woocommerce
|
||||
ref: trunk
|
||||
|
||||
- name: Run npm install.
|
||||
working-directory: package/woocommerce
|
||||
run: npm install
|
||||
|
||||
- name: Move current directory to code. We will install zip file in this dir later.
|
||||
run: mv ./package/woocommerce/* ./code/woocommerce
|
||||
|
||||
- name: Run login test.
|
||||
working-directory: code/woocommerce
|
||||
- name: Install prerequisites.
|
||||
run: |
|
||||
npm install
|
||||
composer install --no-dev
|
||||
npm run build:assets
|
||||
npm install jest
|
||||
|
||||
- name: Run smoke test.
|
||||
env:
|
||||
SMOKE_TEST_URL: ${{ secrets.SMOKE_TEST_URL }}
|
||||
SMOKE_TEST_ADMIN_USER: ${{ secrets.SMOKE_TEST_ADMIN_USER }}
|
||||
SMOKE_TEST_ADMIN_PASSWORD: ${{ secrets.SMOKE_TEST_ADMIN_PASSWORD }}
|
||||
SMOKE_TEST_CUSTOMER_USER: ${{ secrets.SMOKE_TEST_CUSTOMER_USER }}
|
||||
SMOKE_TEST_CUSTOMER_PASSWORD: ${{ secrets.SMOKE_TEST_CUSTOMER_PASSWORD }}
|
||||
run: npx wc-e2e test:e2e ./tests/e2e/specs/activate-and-setup/test-activation.js
|
||||
WC_E2E_SCREENSHOTS: 1
|
||||
E2E_RETEST: 1
|
||||
E2E_SLACK_TOKEN: ${{ secrets.SMOKE_TEST_SLACK_TOKEN }}
|
||||
E2E_SLACK_CHANNEL: ${{ secrets.SMOKE_TEST_SLACK_CHANNEL }}
|
||||
run: |
|
||||
npx wc-e2e docker:up
|
||||
npx wc-e2e test:e2e
|
||||
|
|
|
@ -18,4 +18,3 @@ jobs:
|
|||
only-issue-labels: 'needs feedback'
|
||||
close-issue-label: "category: can't reproduce"
|
||||
ascending: true
|
||||
debug-only: true
|
||||
|
|
|
@ -55,8 +55,11 @@ jQuery( function( $ ) {
|
|||
|
||||
var wc_country_select_select2 = function() {
|
||||
$( 'select.country_select:visible, select.state_select:visible' ).each( function() {
|
||||
var $this = $( this );
|
||||
|
||||
var select2_args = $.extend({
|
||||
placeholder: $( this ).attr( 'data-placeholder' ) || $( this ).attr( 'placeholder' ) || '',
|
||||
placeholder: $this.attr( 'data-placeholder' ) || $this.attr( 'placeholder' ) || '',
|
||||
label: $this.attr( 'data-label' ) || null,
|
||||
width: '100%'
|
||||
}, getEnhancedSelectFormatString() );
|
||||
|
||||
|
|
|
@ -1,27 +1,41 @@
|
|||
/*!
|
||||
* Select2 4.0.3
|
||||
* https://select2.github.io
|
||||
* SelectWoo 1.0.9
|
||||
* https://github.com/woocommerce/selectWoo
|
||||
*
|
||||
* Released under the MIT license
|
||||
* https://github.com/select2/select2/blob/master/LICENSE.md
|
||||
* https://github.com/woocommerce/selectWoo/blob/master/LICENSE.md
|
||||
*/
|
||||
(function (factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['jquery'], factory);
|
||||
} else if (typeof exports === 'object') {
|
||||
} else if (typeof module === 'object' && module.exports) {
|
||||
// Node/CommonJS
|
||||
factory(require('jquery'));
|
||||
module.exports = function (root, jQuery) {
|
||||
if (jQuery === undefined) {
|
||||
// require('jQuery') returns a factory that requires window to
|
||||
// build a jQuery instance, we normalize how we use modules
|
||||
// that require this pattern but the window provided is a noop
|
||||
// if it's defined (how jquery works)
|
||||
if (typeof window !== 'undefined') {
|
||||
jQuery = require('jquery');
|
||||
}
|
||||
else {
|
||||
jQuery = require('jquery')(root);
|
||||
}
|
||||
}
|
||||
factory(jQuery);
|
||||
return jQuery;
|
||||
};
|
||||
} else {
|
||||
// Browser globals
|
||||
factory(jQuery);
|
||||
}
|
||||
}(function (jQuery) {
|
||||
} (function (jQuery) {
|
||||
// This is needed so we can catch the AMD loader configuration and use it
|
||||
// The inner file should be wrapped (by `banner.start.js`) in a function that
|
||||
// returns the AMD loader references.
|
||||
var S2 =
|
||||
(function () {
|
||||
var S2 =(function () {
|
||||
// Restore the Select2 AMD loader so it can be used
|
||||
// Needed mostly in the language files, where the loader is not inserted
|
||||
if (jQuery && jQuery.fn && jQuery.fn.select2 && jQuery.fn.select2.amd) {
|
||||
|
@ -30,13 +44,11 @@
|
|||
var S2;(function () { if (!S2 || !S2.requirejs) {
|
||||
if (!S2) { S2 = {}; } else { require = S2; }
|
||||
/**
|
||||
* @license almond 0.3.1 Copyright (c) 2011-2014, The Dojo Foundation All Rights Reserved.
|
||||
* Available via the MIT or new BSD license.
|
||||
* see: http://github.com/jrburke/almond for details
|
||||
* @license almond 0.3.3 Copyright jQuery Foundation and other contributors.
|
||||
* Released under MIT license, http://github.com/requirejs/almond/LICENSE
|
||||
*/
|
||||
//Going sloppy to avoid 'use strict' string cost, but strict practices should
|
||||
//be followed.
|
||||
/*jslint sloppy: true */
|
||||
/*global setTimeout: false */
|
||||
|
||||
var requirejs, require, define;
|
||||
|
@ -64,60 +76,58 @@ var requirejs, require, define;
|
|||
*/
|
||||
function normalize(name, baseName) {
|
||||
var nameParts, nameSegment, mapValue, foundMap, lastIndex,
|
||||
foundI, foundStarMap, starI, i, j, part,
|
||||
foundI, foundStarMap, starI, i, j, part, normalizedBaseParts,
|
||||
baseParts = baseName && baseName.split("/"),
|
||||
map = config.map,
|
||||
starMap = (map && map['*']) || {};
|
||||
|
||||
//Adjust any relative paths.
|
||||
if (name && name.charAt(0) === ".") {
|
||||
//If have a base name, try to normalize against it,
|
||||
//otherwise, assume it is a top-level require that will
|
||||
//be relative to baseUrl in the end.
|
||||
if (baseName) {
|
||||
name = name.split('/');
|
||||
lastIndex = name.length - 1;
|
||||
if (name) {
|
||||
name = name.split('/');
|
||||
lastIndex = name.length - 1;
|
||||
|
||||
// Node .js allowance:
|
||||
if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
|
||||
name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
|
||||
}
|
||||
// If wanting node ID compatibility, strip .js from end
|
||||
// of IDs. Have to do this here, and not in nameToUrl
|
||||
// because node allows either .js or non .js to map
|
||||
// to same file.
|
||||
if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
|
||||
name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
|
||||
}
|
||||
|
||||
//Lop off the last part of baseParts, so that . matches the
|
||||
//"directory" and not name of the baseName's module. For instance,
|
||||
//baseName of "one/two/three", maps to "one/two/three.js", but we
|
||||
//want the directory, "one/two" for this normalization.
|
||||
name = baseParts.slice(0, baseParts.length - 1).concat(name);
|
||||
// Starts with a '.' so need the baseName
|
||||
if (name[0].charAt(0) === '.' && baseParts) {
|
||||
//Convert baseName to array, and lop off the last part,
|
||||
//so that . matches that 'directory' and not name of the baseName's
|
||||
//module. For instance, baseName of 'one/two/three', maps to
|
||||
//'one/two/three.js', but we want the directory, 'one/two' for
|
||||
//this normalization.
|
||||
normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
|
||||
name = normalizedBaseParts.concat(name);
|
||||
}
|
||||
|
||||
//start trimDots
|
||||
for (i = 0; i < name.length; i += 1) {
|
||||
part = name[i];
|
||||
if (part === ".") {
|
||||
name.splice(i, 1);
|
||||
i -= 1;
|
||||
} else if (part === "..") {
|
||||
if (i === 1 && (name[2] === '..' || name[0] === '..')) {
|
||||
//End of the line. Keep at least one non-dot
|
||||
//path segment at the front so it can be mapped
|
||||
//correctly to disk. Otherwise, there is likely
|
||||
//no path mapping for a path starting with '..'.
|
||||
//This can still fail, but catches the most reasonable
|
||||
//uses of ..
|
||||
break;
|
||||
} else if (i > 0) {
|
||||
name.splice(i - 1, 2);
|
||||
i -= 2;
|
||||
}
|
||||
//start trimDots
|
||||
for (i = 0; i < name.length; i++) {
|
||||
part = name[i];
|
||||
if (part === '.') {
|
||||
name.splice(i, 1);
|
||||
i -= 1;
|
||||
} else if (part === '..') {
|
||||
// If at the start, or previous value is still ..,
|
||||
// keep them so that when converted to a path it may
|
||||
// still work when converted to a path, even though
|
||||
// as an ID it is less than ideal. In larger point
|
||||
// releases, may be better to just kick out an error.
|
||||
if (i === 0 || (i === 1 && name[2] === '..') || name[i - 1] === '..') {
|
||||
continue;
|
||||
} else if (i > 0) {
|
||||
name.splice(i - 1, 2);
|
||||
i -= 2;
|
||||
}
|
||||
}
|
||||
//end trimDots
|
||||
|
||||
name = name.join("/");
|
||||
} else if (name.indexOf('./') === 0) {
|
||||
// No baseName, so this is ID is resolved relative
|
||||
// to baseUrl, pull off the leading dot.
|
||||
name = name.substring(2);
|
||||
}
|
||||
//end trimDots
|
||||
|
||||
name = name.join('/');
|
||||
}
|
||||
|
||||
//Apply map config if available.
|
||||
|
@ -230,32 +240,39 @@ var requirejs, require, define;
|
|||
return [prefix, name];
|
||||
}
|
||||
|
||||
//Creates a parts array for a relName where first part is plugin ID,
|
||||
//second part is resource ID. Assumes relName has already been normalized.
|
||||
function makeRelParts(relName) {
|
||||
return relName ? splitPrefix(relName) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a name map, normalizing the name, and using a plugin
|
||||
* for normalization if necessary. Grabs a ref to plugin
|
||||
* too, as an optimization.
|
||||
*/
|
||||
makeMap = function (name, relName) {
|
||||
makeMap = function (name, relParts) {
|
||||
var plugin,
|
||||
parts = splitPrefix(name),
|
||||
prefix = parts[0];
|
||||
prefix = parts[0],
|
||||
relResourceName = relParts[1];
|
||||
|
||||
name = parts[1];
|
||||
|
||||
if (prefix) {
|
||||
prefix = normalize(prefix, relName);
|
||||
prefix = normalize(prefix, relResourceName);
|
||||
plugin = callDep(prefix);
|
||||
}
|
||||
|
||||
//Normalize according
|
||||
if (prefix) {
|
||||
if (plugin && plugin.normalize) {
|
||||
name = plugin.normalize(name, makeNormalize(relName));
|
||||
name = plugin.normalize(name, makeNormalize(relResourceName));
|
||||
} else {
|
||||
name = normalize(name, relName);
|
||||
name = normalize(name, relResourceName);
|
||||
}
|
||||
} else {
|
||||
name = normalize(name, relName);
|
||||
name = normalize(name, relResourceName);
|
||||
parts = splitPrefix(name);
|
||||
prefix = parts[0];
|
||||
name = parts[1];
|
||||
|
@ -302,13 +319,14 @@ var requirejs, require, define;
|
|||
};
|
||||
|
||||
main = function (name, deps, callback, relName) {
|
||||
var cjsModule, depName, ret, map, i,
|
||||
var cjsModule, depName, ret, map, i, relParts,
|
||||
args = [],
|
||||
callbackType = typeof callback,
|
||||
usingExports;
|
||||
|
||||
//Use name if no relName
|
||||
relName = relName || name;
|
||||
relParts = makeRelParts(relName);
|
||||
|
||||
//Call the callback to define the module, if necessary.
|
||||
if (callbackType === 'undefined' || callbackType === 'function') {
|
||||
|
@ -317,7 +335,7 @@ var requirejs, require, define;
|
|||
//Default to [require, exports, module] if no deps
|
||||
deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
|
||||
for (i = 0; i < deps.length; i += 1) {
|
||||
map = makeMap(deps[i], relName);
|
||||
map = makeMap(deps[i], relParts);
|
||||
depName = map.f;
|
||||
|
||||
//Fast path CommonJS standard dependencies.
|
||||
|
@ -373,7 +391,7 @@ var requirejs, require, define;
|
|||
//deps arg is the module name, and second arg (if passed)
|
||||
//is just the relName.
|
||||
//Normalize module name, if it contains . or ..
|
||||
return callDep(makeMap(deps, callback).f);
|
||||
return callDep(makeMap(deps, makeRelParts(callback)).f);
|
||||
} else if (!deps.splice) {
|
||||
//deps is a config object, not an array.
|
||||
config = deps;
|
||||
|
@ -737,6 +755,12 @@ S2.define('select2/utils',[
|
|||
});
|
||||
};
|
||||
|
||||
Utils.entityDecode = function (html) {
|
||||
var txt = document.createElement('textarea');
|
||||
txt.innerHTML = html;
|
||||
return txt.value;
|
||||
}
|
||||
|
||||
// Append an array of jQuery nodes to a given element.
|
||||
Utils.appendMany = function ($element, $nodes) {
|
||||
// jQuery 1.7.x does not support $.fn.append() with an array
|
||||
|
@ -754,6 +778,14 @@ S2.define('select2/utils',[
|
|||
$element.append($nodes);
|
||||
};
|
||||
|
||||
// Determine whether the browser is on a touchscreen device.
|
||||
Utils.isTouchscreen = function() {
|
||||
if ('undefined' === typeof Utils._isTouchscreenCache) {
|
||||
Utils._isTouchscreenCache = 'ontouchstart' in document.documentElement;
|
||||
}
|
||||
return Utils._isTouchscreenCache;
|
||||
}
|
||||
|
||||
return Utils;
|
||||
});
|
||||
|
||||
|
@ -773,7 +805,7 @@ S2.define('select2/results',[
|
|||
|
||||
Results.prototype.render = function () {
|
||||
var $results = $(
|
||||
'<ul class="select2-results__options" role="tree"></ul>'
|
||||
'<ul class="select2-results__options" role="listbox" tabindex="-1"></ul>'
|
||||
);
|
||||
|
||||
if (this.options.get('multiple')) {
|
||||
|
@ -796,7 +828,7 @@ S2.define('select2/results',[
|
|||
this.hideLoading();
|
||||
|
||||
var $message = $(
|
||||
'<li role="treeitem" aria-live="assertive"' +
|
||||
'<li role="alert" aria-live="assertive"' +
|
||||
' class="select2-results__option"></li>'
|
||||
);
|
||||
|
||||
|
@ -858,9 +890,9 @@ S2.define('select2/results',[
|
|||
|
||||
Results.prototype.highlightFirstItem = function () {
|
||||
var $options = this.$results
|
||||
.find('.select2-results__option[aria-selected]');
|
||||
.find('.select2-results__option[data-selected]');
|
||||
|
||||
var $selected = $options.filter('[aria-selected=true]');
|
||||
var $selected = $options.filter('[data-selected=true]');
|
||||
|
||||
// Check if there are any selected options
|
||||
if ($selected.length > 0) {
|
||||
|
@ -884,7 +916,7 @@ S2.define('select2/results',[
|
|||
});
|
||||
|
||||
var $options = self.$results
|
||||
.find('.select2-results__option[aria-selected]');
|
||||
.find('.select2-results__option[data-selected]');
|
||||
|
||||
$options.each(function () {
|
||||
var $option = $(this);
|
||||
|
@ -896,9 +928,9 @@ S2.define('select2/results',[
|
|||
|
||||
if ((item.element != null && item.element.selected) ||
|
||||
(item.element == null && $.inArray(id, selectedIds) > -1)) {
|
||||
$option.attr('aria-selected', 'true');
|
||||
$option.attr('data-selected', 'true');
|
||||
} else {
|
||||
$option.attr('aria-selected', 'false');
|
||||
$option.attr('data-selected', 'false');
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -930,17 +962,18 @@ S2.define('select2/results',[
|
|||
option.className = 'select2-results__option';
|
||||
|
||||
var attrs = {
|
||||
'role': 'treeitem',
|
||||
'aria-selected': 'false'
|
||||
'role': 'option',
|
||||
'data-selected': 'false',
|
||||
'tabindex': -1
|
||||
};
|
||||
|
||||
if (data.disabled) {
|
||||
delete attrs['aria-selected'];
|
||||
delete attrs['data-selected'];
|
||||
attrs['aria-disabled'] = 'true';
|
||||
}
|
||||
|
||||
if (data.id == null) {
|
||||
delete attrs['aria-selected'];
|
||||
delete attrs['data-selected'];
|
||||
}
|
||||
|
||||
if (data._resultId != null) {
|
||||
|
@ -952,9 +985,8 @@ S2.define('select2/results',[
|
|||
}
|
||||
|
||||
if (data.children) {
|
||||
attrs.role = 'group';
|
||||
attrs['aria-label'] = data.text;
|
||||
delete attrs['aria-selected'];
|
||||
delete attrs['data-selected'];
|
||||
}
|
||||
|
||||
for (var attr in attrs) {
|
||||
|
@ -971,6 +1003,7 @@ S2.define('select2/results',[
|
|||
|
||||
var $label = $(label);
|
||||
this.template(data, label);
|
||||
$label.attr('role', 'presentation');
|
||||
|
||||
var $children = [];
|
||||
|
||||
|
@ -983,10 +1016,11 @@ S2.define('select2/results',[
|
|||
}
|
||||
|
||||
var $childrenContainer = $('<ul></ul>', {
|
||||
'class': 'select2-results__options select2-results__options--nested'
|
||||
'class': 'select2-results__options select2-results__options--nested',
|
||||
'role': 'listbox'
|
||||
});
|
||||
|
||||
$childrenContainer.append($children);
|
||||
$option.attr('role', 'list');
|
||||
|
||||
$option.append(label);
|
||||
$option.append($childrenContainer);
|
||||
|
@ -1082,7 +1116,7 @@ S2.define('select2/results',[
|
|||
|
||||
var data = $highlighted.data('data');
|
||||
|
||||
if ($highlighted.attr('aria-selected') == 'true') {
|
||||
if ($highlighted.attr('data-selected') == 'true') {
|
||||
self.trigger('close', {});
|
||||
} else {
|
||||
self.trigger('select', {
|
||||
|
@ -1094,7 +1128,7 @@ S2.define('select2/results',[
|
|||
container.on('results:previous', function () {
|
||||
var $highlighted = self.getHighlightedResults();
|
||||
|
||||
var $options = self.$results.find('[aria-selected]');
|
||||
var $options = self.$results.find('[data-selected]');
|
||||
|
||||
var currentIndex = $options.index($highlighted);
|
||||
|
||||
|
@ -1128,7 +1162,7 @@ S2.define('select2/results',[
|
|||
container.on('results:next', function () {
|
||||
var $highlighted = self.getHighlightedResults();
|
||||
|
||||
var $options = self.$results.find('[aria-selected]');
|
||||
var $options = self.$results.find('[data-selected]');
|
||||
|
||||
var currentIndex = $options.index($highlighted);
|
||||
|
||||
|
@ -1156,7 +1190,8 @@ S2.define('select2/results',[
|
|||
});
|
||||
|
||||
container.on('results:focus', function (params) {
|
||||
params.element.addClass('select2-results__option--highlighted');
|
||||
params.element.addClass('select2-results__option--highlighted').attr('aria-selected', 'true');
|
||||
self.$results.attr('aria-activedescendant', params.element.attr('id'));
|
||||
});
|
||||
|
||||
container.on('results:message', function (params) {
|
||||
|
@ -1188,13 +1223,13 @@ S2.define('select2/results',[
|
|||
});
|
||||
}
|
||||
|
||||
this.$results.on('mouseup', '.select2-results__option[aria-selected]',
|
||||
this.$results.on('mouseup', '.select2-results__option[data-selected]',
|
||||
function (evt) {
|
||||
var $this = $(this);
|
||||
|
||||
var data = $this.data('data');
|
||||
|
||||
if ($this.attr('aria-selected') === 'true') {
|
||||
if ($this.attr('data-selected') === 'true') {
|
||||
if (self.options.get('multiple')) {
|
||||
self.trigger('unselect', {
|
||||
originalEvent: evt,
|
||||
|
@ -1213,12 +1248,13 @@ S2.define('select2/results',[
|
|||
});
|
||||
});
|
||||
|
||||
this.$results.on('mouseenter', '.select2-results__option[aria-selected]',
|
||||
this.$results.on('mouseenter', '.select2-results__option[data-selected]',
|
||||
function (evt) {
|
||||
var data = $(this).data('data');
|
||||
|
||||
self.getHighlightedResults()
|
||||
.removeClass('select2-results__option--highlighted');
|
||||
.removeClass('select2-results__option--highlighted')
|
||||
.attr('aria-selected', 'false');
|
||||
|
||||
self.trigger('results:focus', {
|
||||
data: data,
|
||||
|
@ -1245,7 +1281,7 @@ S2.define('select2/results',[
|
|||
return;
|
||||
}
|
||||
|
||||
var $options = this.$results.find('[aria-selected]');
|
||||
var $options = this.$results.find('[data-selected]');
|
||||
|
||||
var currentIndex = $options.index($highlighted);
|
||||
|
||||
|
@ -1323,7 +1359,7 @@ S2.define('select2/selection/base',[
|
|||
|
||||
BaseSelection.prototype.render = function () {
|
||||
var $selection = $(
|
||||
'<span class="select2-selection" role="combobox" ' +
|
||||
'<span class="select2-selection" ' +
|
||||
' aria-haspopup="true" aria-expanded="false">' +
|
||||
'</span>'
|
||||
);
|
||||
|
@ -1349,6 +1385,7 @@ S2.define('select2/selection/base',[
|
|||
|
||||
var id = container.id + '-container';
|
||||
var resultsId = container.id + '-results';
|
||||
var searchHidden = this.options.get('minimumResultsForSearch') === Infinity;
|
||||
|
||||
this.container = container;
|
||||
|
||||
|
@ -1390,7 +1427,11 @@ S2.define('select2/selection/base',[
|
|||
self.$selection.removeAttr('aria-activedescendant');
|
||||
self.$selection.removeAttr('aria-owns');
|
||||
|
||||
self.$selection.focus();
|
||||
// This needs to be delayed as the active element is the body when the
|
||||
// key is pressed.
|
||||
window.setTimeout(function () {
|
||||
self.$selection.focus();
|
||||
}, 1);
|
||||
|
||||
self._detachCloseHandler(container);
|
||||
});
|
||||
|
@ -1440,8 +1481,14 @@ S2.define('select2/selection/base',[
|
|||
}
|
||||
|
||||
var $element = $this.data('element');
|
||||
|
||||
$element.select2('close');
|
||||
|
||||
// Remove any focus when dropdown is closed by clicking outside the select area.
|
||||
// Timeout of 1 required for close to finish wrapping up.
|
||||
setTimeout(function(){
|
||||
$this.find('*:focus').blur();
|
||||
$target.focus();
|
||||
}, 1);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
@ -1500,8 +1547,21 @@ S2.define('select2/selection/single',[
|
|||
|
||||
var id = container.id + '-container';
|
||||
|
||||
this.$selection.find('.select2-selection__rendered').attr('id', id);
|
||||
this.$selection.attr('aria-labelledby', id);
|
||||
this.$selection.find('.select2-selection__rendered')
|
||||
.attr('id', id)
|
||||
.attr('role', 'textbox')
|
||||
.attr('aria-readonly', 'true');
|
||||
|
||||
var label = this.options.get( 'label' );
|
||||
|
||||
if ( typeof( label ) === 'string' ) {
|
||||
this.$selection.attr( 'aria-label', label );
|
||||
} else {
|
||||
this.$selection.attr( 'aria-labelledby', id );
|
||||
}
|
||||
|
||||
// This makes single non-search selects work in screen readers. If it causes problems elsewhere, remove.
|
||||
this.$selection.attr('role', 'combobox');
|
||||
|
||||
this.$selection.on('mousedown', function (evt) {
|
||||
// Only respond to left clicks
|
||||
|
@ -1518,6 +1578,13 @@ S2.define('select2/selection/single',[
|
|||
// User focuses on the container
|
||||
});
|
||||
|
||||
this.$selection.on('keydown', function (evt) {
|
||||
// If user starts typing an alphanumeric key on the keyboard, open if not opened.
|
||||
if (!container.isOpen() && evt.which >= 48 && evt.which <= 90) {
|
||||
container.open();
|
||||
}
|
||||
});
|
||||
|
||||
this.$selection.on('blur', function (evt) {
|
||||
// User exits the container
|
||||
});
|
||||
|
@ -1557,9 +1624,9 @@ S2.define('select2/selection/single',[
|
|||
var selection = data[0];
|
||||
|
||||
var $rendered = this.$selection.find('.select2-selection__rendered');
|
||||
var formatted = this.display(selection, $rendered);
|
||||
var formatted = Utils.entityDecode(this.display(selection, $rendered));
|
||||
|
||||
$rendered.empty().append(formatted);
|
||||
$rendered.empty().text(formatted);
|
||||
$rendered.prop('title', selection.title || selection.text);
|
||||
};
|
||||
|
||||
|
@ -1583,7 +1650,7 @@ S2.define('select2/selection/multiple',[
|
|||
$selection.addClass('select2-selection--multiple');
|
||||
|
||||
$selection.html(
|
||||
'<ul class="select2-selection__rendered"></ul>'
|
||||
'<ul class="select2-selection__rendered" aria-live="polite" aria-relevant="additions removals" aria-atomic="true"></ul>'
|
||||
);
|
||||
|
||||
return $selection;
|
||||
|
@ -1620,6 +1687,18 @@ S2.define('select2/selection/multiple',[
|
|||
});
|
||||
}
|
||||
);
|
||||
|
||||
this.$selection.on('keydown', function (evt) {
|
||||
// If user starts typing an alphanumeric key on the keyboard, open if not opened.
|
||||
if (!container.isOpen() && evt.which >= 48 && evt.which <= 90) {
|
||||
container.open();
|
||||
}
|
||||
});
|
||||
|
||||
// Focus on the search field when the container is focused instead of the main container.
|
||||
container.on( 'focus', function(){
|
||||
self.focusOnSearch();
|
||||
});
|
||||
};
|
||||
|
||||
MultipleSelection.prototype.clear = function () {
|
||||
|
@ -1636,7 +1715,7 @@ S2.define('select2/selection/multiple',[
|
|||
MultipleSelection.prototype.selectionContainer = function () {
|
||||
var $container = $(
|
||||
'<li class="select2-selection__choice">' +
|
||||
'<span class="select2-selection__choice__remove" role="presentation">' +
|
||||
'<span class="select2-selection__choice__remove" role="presentation" aria-hidden="true">' +
|
||||
'×' +
|
||||
'</span>' +
|
||||
'</li>'
|
||||
|
@ -1645,6 +1724,24 @@ S2.define('select2/selection/multiple',[
|
|||
return $container;
|
||||
};
|
||||
|
||||
/**
|
||||
* Focus on the search field instead of the main multiselect container.
|
||||
*/
|
||||
MultipleSelection.prototype.focusOnSearch = function() {
|
||||
var self = this;
|
||||
|
||||
if ('undefined' !== typeof self.$search) {
|
||||
// Needs 1 ms delay because of other 1 ms setTimeouts when rendering.
|
||||
setTimeout(function(){
|
||||
// Prevent the dropdown opening again when focused from this.
|
||||
// This gets reset automatically when focus is triggered.
|
||||
self._keyUpPrevented = true;
|
||||
|
||||
self.$search.focus();
|
||||
}, 1);
|
||||
}
|
||||
}
|
||||
|
||||
MultipleSelection.prototype.update = function (data) {
|
||||
this.clear();
|
||||
|
||||
|
@ -1658,9 +1755,14 @@ S2.define('select2/selection/multiple',[
|
|||
var selection = data[d];
|
||||
|
||||
var $selection = this.selectionContainer();
|
||||
var removeItemTag = $selection.html();
|
||||
var formatted = this.display(selection, $selection);
|
||||
if ('string' === typeof formatted) {
|
||||
formatted = Utils.entityDecode(formatted.trim());
|
||||
}
|
||||
|
||||
$selection.append(formatted);
|
||||
$selection.text(formatted);
|
||||
$selection.prepend(removeItemTag);
|
||||
$selection.prop('title', selection.title || selection.text);
|
||||
|
||||
$selection.data('data', selection);
|
||||
|
@ -1699,7 +1801,7 @@ S2.define('select2/selection/placeholder',[
|
|||
Placeholder.prototype.createPlaceholder = function (decorated, placeholder) {
|
||||
var $placeholder = this.selectionContainer();
|
||||
|
||||
$placeholder.html(this.display(placeholder));
|
||||
$placeholder.text(Utils.entityDecode(this.display(placeholder)));
|
||||
$placeholder.addClass('select2-selection__placeholder')
|
||||
.removeClass('select2-selection__choice');
|
||||
|
||||
|
@ -1836,8 +1938,8 @@ S2.define('select2/selection/search',[
|
|||
Search.prototype.render = function (decorated) {
|
||||
var $search = $(
|
||||
'<li class="select2-search select2-search--inline">' +
|
||||
'<input class="select2-search__field" type="search" tabindex="-1"' +
|
||||
' autocomplete="off" autocorrect="off" autocapitalize="off"' +
|
||||
'<input class="select2-search__field" type="text" tabindex="-1"' +
|
||||
' autocomplete="off" autocorrect="off" autocapitalize="none"' +
|
||||
' spellcheck="false" role="textbox" aria-autocomplete="list" />' +
|
||||
'</li>'
|
||||
);
|
||||
|
@ -1854,16 +1956,19 @@ S2.define('select2/selection/search',[
|
|||
|
||||
Search.prototype.bind = function (decorated, container, $container) {
|
||||
var self = this;
|
||||
var resultsId = container.id + '-results';
|
||||
|
||||
decorated.call(this, container, $container);
|
||||
|
||||
container.on('open', function () {
|
||||
self.$search.attr('aria-owns', resultsId);
|
||||
self.$search.trigger('focus');
|
||||
});
|
||||
|
||||
container.on('close', function () {
|
||||
self.$search.val('');
|
||||
self.$search.removeAttr('aria-activedescendant');
|
||||
self.$search.removeAttr('aria-owns');
|
||||
self.$search.trigger('focus');
|
||||
});
|
||||
|
||||
|
@ -1882,7 +1987,7 @@ S2.define('select2/selection/search',[
|
|||
});
|
||||
|
||||
container.on('results:focus', function (params) {
|
||||
self.$search.attr('aria-activedescendant', params.id);
|
||||
self.$search.attr('aria-activedescendant', params.data._resultId);
|
||||
});
|
||||
|
||||
this.$selection.on('focusin', '.select2-search--inline', function (evt) {
|
||||
|
@ -1913,6 +2018,9 @@ S2.define('select2/selection/search',[
|
|||
|
||||
evt.preventDefault();
|
||||
}
|
||||
} else if (evt.which === KEYS.ENTER) {
|
||||
container.open();
|
||||
evt.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -3004,8 +3112,15 @@ S2.define('select2/data/base',[
|
|||
};
|
||||
|
||||
BaseAdapter.prototype.generateResultId = function (container, data) {
|
||||
var id = container.id + '-result-';
|
||||
var id = '';
|
||||
|
||||
if (container != null) {
|
||||
id += container.id
|
||||
} else {
|
||||
id += Utils.generateChars(4);
|
||||
}
|
||||
|
||||
id += '-result-';
|
||||
id += Utils.generateChars(4);
|
||||
|
||||
if (data.id != null) {
|
||||
|
@ -3191,7 +3306,7 @@ S2.define('select2/data/select',[
|
|||
}
|
||||
}
|
||||
|
||||
if (data.id) {
|
||||
if (data.id !== undefined) {
|
||||
option.value = data.id;
|
||||
}
|
||||
|
||||
|
@ -3289,7 +3404,7 @@ S2.define('select2/data/select',[
|
|||
item.text = item.text.toString();
|
||||
}
|
||||
|
||||
if (item._resultId == null && item.id && this.container != null) {
|
||||
if (item._resultId == null && item.id) {
|
||||
item._resultId = this.generateResultId(this.container, item);
|
||||
}
|
||||
|
||||
|
@ -3466,6 +3581,7 @@ S2.define('select2/data/ajax',[
|
|||
}
|
||||
|
||||
callback(results);
|
||||
self.container.focusOnActiveElement();
|
||||
}, function () {
|
||||
// Attempt to detect if a request was aborted
|
||||
// Only works if the transport exposes a status property
|
||||
|
@ -3550,7 +3666,10 @@ S2.define('select2/data/tags',[
|
|||
}, true)
|
||||
);
|
||||
|
||||
var checkText = option.text === params.term;
|
||||
var optionText = (option.text || '').toUpperCase();
|
||||
var paramsTerm = (params.term || '').toUpperCase();
|
||||
|
||||
var checkText = optionText === paramsTerm;
|
||||
|
||||
if (checkText || checkChildren) {
|
||||
if (child) {
|
||||
|
@ -3887,9 +4006,9 @@ S2.define('select2/dropdown/search',[
|
|||
|
||||
var $search = $(
|
||||
'<span class="select2-search select2-search--dropdown">' +
|
||||
'<input class="select2-search__field" type="search" tabindex="-1"' +
|
||||
' autocomplete="off" autocorrect="off" autocapitalize="off"' +
|
||||
' spellcheck="false" role="textbox" />' +
|
||||
'<input class="select2-search__field" type="text" tabindex="-1"' +
|
||||
' autocomplete="off" autocorrect="off" autocapitalize="none"' +
|
||||
' spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" />' +
|
||||
'</span>'
|
||||
);
|
||||
|
||||
|
@ -3903,6 +4022,7 @@ S2.define('select2/dropdown/search',[
|
|||
|
||||
Search.prototype.bind = function (decorated, container, $container) {
|
||||
var self = this;
|
||||
var resultsId = container.id + '-results';
|
||||
|
||||
decorated.call(this, container, $container);
|
||||
|
||||
|
@ -3926,7 +4046,7 @@ S2.define('select2/dropdown/search',[
|
|||
|
||||
container.on('open', function () {
|
||||
self.$search.attr('tabindex', 0);
|
||||
|
||||
self.$search.attr('aria-owns', resultsId);
|
||||
self.$search.focus();
|
||||
|
||||
window.setTimeout(function () {
|
||||
|
@ -3936,12 +4056,13 @@ S2.define('select2/dropdown/search',[
|
|||
|
||||
container.on('close', function () {
|
||||
self.$search.attr('tabindex', -1);
|
||||
|
||||
self.$search.removeAttr('aria-activedescendant');
|
||||
self.$search.removeAttr('aria-owns');
|
||||
self.$search.val('');
|
||||
});
|
||||
|
||||
container.on('focus', function () {
|
||||
if (container.isOpen()) {
|
||||
if (!container.isOpen()) {
|
||||
self.$search.focus();
|
||||
}
|
||||
});
|
||||
|
@ -3957,6 +4078,10 @@ S2.define('select2/dropdown/search',[
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
container.on('results:focus', function (params) {
|
||||
self.$search.attr('aria-activedescendant', params.data._resultId);
|
||||
});
|
||||
};
|
||||
|
||||
Search.prototype.handleSearch = function (evt) {
|
||||
|
@ -4098,7 +4223,7 @@ S2.define('select2/dropdown/infiniteScroll',[
|
|||
var $option = $(
|
||||
'<li ' +
|
||||
'class="select2-results__option select2-results__option--load-more"' +
|
||||
'role="treeitem" aria-disabled="true"></li>'
|
||||
'role="option" aria-disabled="true"></li>'
|
||||
);
|
||||
|
||||
var message = this.options.get('translations').get('loadingMore');
|
||||
|
@ -5344,16 +5469,22 @@ S2.define('select2/core',[
|
|||
});
|
||||
});
|
||||
|
||||
this.on('keypress', function (evt) {
|
||||
var key = evt.which;
|
||||
this.on('open', function(){
|
||||
// Focus on the active element when opening dropdown.
|
||||
// Needs 1 ms delay because of other 1 ms setTimeouts when rendering.
|
||||
setTimeout(function(){
|
||||
self.focusOnActiveElement();
|
||||
}, 1);
|
||||
});
|
||||
|
||||
$(document).on('keydown', function (evt) {
|
||||
var key = evt.which;
|
||||
if (self.isOpen()) {
|
||||
if (key === KEYS.ESC || key === KEYS.TAB ||
|
||||
(key === KEYS.UP && evt.altKey)) {
|
||||
if (key === KEYS.ESC || (key === KEYS.UP && evt.altKey)) {
|
||||
self.close();
|
||||
|
||||
evt.preventDefault();
|
||||
} else if (key === KEYS.ENTER) {
|
||||
} else if (key === KEYS.ENTER || key === KEYS.TAB) {
|
||||
self.trigger('results:select', {});
|
||||
|
||||
evt.preventDefault();
|
||||
|
@ -5370,17 +5501,42 @@ S2.define('select2/core',[
|
|||
|
||||
evt.preventDefault();
|
||||
}
|
||||
} else {
|
||||
if (key === KEYS.ENTER || key === KEYS.SPACE ||
|
||||
(key === KEYS.DOWN && evt.altKey)) {
|
||||
self.open();
|
||||
|
||||
var $searchField = self.$dropdown.find('.select2-search__field');
|
||||
if (! $searchField.length) {
|
||||
$searchField = self.$container.find('.select2-search__field');
|
||||
}
|
||||
|
||||
// Move the focus to the selected element on keyboard navigation.
|
||||
// Required for screen readers to work properly.
|
||||
if (key === KEYS.DOWN || key === KEYS.UP) {
|
||||
self.focusOnActiveElement();
|
||||
} else {
|
||||
// Focus on the search if user starts typing.
|
||||
$searchField.focus();
|
||||
// Focus back to active selection when finished typing.
|
||||
// Small delay so typed character can be read by screen reader.
|
||||
setTimeout(function(){
|
||||
self.focusOnActiveElement();
|
||||
}, 1000);
|
||||
}
|
||||
} else if (self.hasFocus()) {
|
||||
if (key === KEYS.ENTER || key === KEYS.SPACE ||
|
||||
key === KEYS.DOWN) {
|
||||
self.open();
|
||||
evt.preventDefault();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Select2.prototype.focusOnActiveElement = function () {
|
||||
// Don't mess with the focus on touchscreens because it causes havoc with on-screen keyboards.
|
||||
if (this.isOpen() && ! Utils.isTouchscreen()) {
|
||||
this.$results.find('li.select2-results__option--highlighted').focus();
|
||||
}
|
||||
};
|
||||
|
||||
Select2.prototype._syncAttributes = function () {
|
||||
this.options.set('disabled', this.$element.prop('disabled'));
|
||||
|
||||
|
@ -6364,11 +6520,11 @@ S2.define('jquery.select2',[
|
|||
'./select2/core',
|
||||
'./select2/defaults'
|
||||
], function ($, _, Select2, Defaults) {
|
||||
if ($.fn.select2 == null) {
|
||||
if ($.fn.selectWoo == null) {
|
||||
// All methods that should return the element
|
||||
var thisMethods = ['open', 'close', 'destroy'];
|
||||
|
||||
$.fn.select2 = function (options) {
|
||||
$.fn.selectWoo = function (options) {
|
||||
options = options || {};
|
||||
|
||||
if (typeof options === 'object') {
|
||||
|
@ -6408,10 +6564,17 @@ S2.define('jquery.select2',[
|
|||
};
|
||||
}
|
||||
|
||||
if ($.fn.select2.defaults == null) {
|
||||
$.fn.select2.defaults = Defaults;
|
||||
if ($.fn.select2 != null && $.fn.select2.defaults != null) {
|
||||
$.fn.selectWoo.defaults = $.fn.select2.defaults;
|
||||
}
|
||||
|
||||
if ($.fn.selectWoo.defaults == null) {
|
||||
$.fn.selectWoo.defaults = Defaults;
|
||||
}
|
||||
|
||||
// Also register selectWoo under select2 if select2 is not already present.
|
||||
$.fn.select2 = $.fn.select2 || $.fn.selectWoo;
|
||||
|
||||
return Select2;
|
||||
});
|
||||
|
||||
|
@ -6430,6 +6593,7 @@ S2.define('jquery.select2',[
|
|||
// This allows Select2 to use the internal loader outside of this file, such
|
||||
// as in the language files.
|
||||
jQuery.fn.select2.amd = S2;
|
||||
jQuery.fn.selectWoo.amd = S2;
|
||||
|
||||
// Return the Select2 instance for anyone who is importing it.
|
||||
return select2;
|
||||
|
|
|
@ -1,27 +1,41 @@
|
|||
/*!
|
||||
* Select2 4.0.3
|
||||
* https://select2.github.io
|
||||
* SelectWoo 1.0.9
|
||||
* https://github.com/woocommerce/selectWoo
|
||||
*
|
||||
* Released under the MIT license
|
||||
* https://github.com/select2/select2/blob/master/LICENSE.md
|
||||
* https://github.com/woocommerce/selectWoo/blob/master/LICENSE.md
|
||||
*/
|
||||
(function (factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['jquery'], factory);
|
||||
} else if (typeof exports === 'object') {
|
||||
} else if (typeof module === 'object' && module.exports) {
|
||||
// Node/CommonJS
|
||||
factory(require('jquery'));
|
||||
module.exports = function (root, jQuery) {
|
||||
if (jQuery === undefined) {
|
||||
// require('jQuery') returns a factory that requires window to
|
||||
// build a jQuery instance, we normalize how we use modules
|
||||
// that require this pattern but the window provided is a noop
|
||||
// if it's defined (how jquery works)
|
||||
if (typeof window !== 'undefined') {
|
||||
jQuery = require('jquery');
|
||||
}
|
||||
else {
|
||||
jQuery = require('jquery')(root);
|
||||
}
|
||||
}
|
||||
factory(jQuery);
|
||||
return jQuery;
|
||||
};
|
||||
} else {
|
||||
// Browser globals
|
||||
factory(jQuery);
|
||||
}
|
||||
}(function (jQuery) {
|
||||
} (function (jQuery) {
|
||||
// This is needed so we can catch the AMD loader configuration and use it
|
||||
// The inner file should be wrapped (by `banner.start.js`) in a function that
|
||||
// returns the AMD loader references.
|
||||
var S2 =
|
||||
(function () {
|
||||
var S2 =(function () {
|
||||
// Restore the Select2 AMD loader so it can be used
|
||||
// Needed mostly in the language files, where the loader is not inserted
|
||||
if (jQuery && jQuery.fn && jQuery.fn.select2 && jQuery.fn.select2.amd) {
|
||||
|
@ -30,13 +44,11 @@
|
|||
var S2;(function () { if (!S2 || !S2.requirejs) {
|
||||
if (!S2) { S2 = {}; } else { require = S2; }
|
||||
/**
|
||||
* @license almond 0.3.1 Copyright (c) 2011-2014, The Dojo Foundation All Rights Reserved.
|
||||
* Available via the MIT or new BSD license.
|
||||
* see: http://github.com/jrburke/almond for details
|
||||
* @license almond 0.3.3 Copyright jQuery Foundation and other contributors.
|
||||
* Released under MIT license, http://github.com/requirejs/almond/LICENSE
|
||||
*/
|
||||
//Going sloppy to avoid 'use strict' string cost, but strict practices should
|
||||
//be followed.
|
||||
/*jslint sloppy: true */
|
||||
/*global setTimeout: false */
|
||||
|
||||
var requirejs, require, define;
|
||||
|
@ -64,60 +76,58 @@ var requirejs, require, define;
|
|||
*/
|
||||
function normalize(name, baseName) {
|
||||
var nameParts, nameSegment, mapValue, foundMap, lastIndex,
|
||||
foundI, foundStarMap, starI, i, j, part,
|
||||
foundI, foundStarMap, starI, i, j, part, normalizedBaseParts,
|
||||
baseParts = baseName && baseName.split("/"),
|
||||
map = config.map,
|
||||
starMap = (map && map['*']) || {};
|
||||
|
||||
//Adjust any relative paths.
|
||||
if (name && name.charAt(0) === ".") {
|
||||
//If have a base name, try to normalize against it,
|
||||
//otherwise, assume it is a top-level require that will
|
||||
//be relative to baseUrl in the end.
|
||||
if (baseName) {
|
||||
name = name.split('/');
|
||||
lastIndex = name.length - 1;
|
||||
if (name) {
|
||||
name = name.split('/');
|
||||
lastIndex = name.length - 1;
|
||||
|
||||
// Node .js allowance:
|
||||
if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
|
||||
name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
|
||||
}
|
||||
// If wanting node ID compatibility, strip .js from end
|
||||
// of IDs. Have to do this here, and not in nameToUrl
|
||||
// because node allows either .js or non .js to map
|
||||
// to same file.
|
||||
if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
|
||||
name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
|
||||
}
|
||||
|
||||
//Lop off the last part of baseParts, so that . matches the
|
||||
//"directory" and not name of the baseName's module. For instance,
|
||||
//baseName of "one/two/three", maps to "one/two/three.js", but we
|
||||
//want the directory, "one/two" for this normalization.
|
||||
name = baseParts.slice(0, baseParts.length - 1).concat(name);
|
||||
// Starts with a '.' so need the baseName
|
||||
if (name[0].charAt(0) === '.' && baseParts) {
|
||||
//Convert baseName to array, and lop off the last part,
|
||||
//so that . matches that 'directory' and not name of the baseName's
|
||||
//module. For instance, baseName of 'one/two/three', maps to
|
||||
//'one/two/three.js', but we want the directory, 'one/two' for
|
||||
//this normalization.
|
||||
normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
|
||||
name = normalizedBaseParts.concat(name);
|
||||
}
|
||||
|
||||
//start trimDots
|
||||
for (i = 0; i < name.length; i += 1) {
|
||||
part = name[i];
|
||||
if (part === ".") {
|
||||
name.splice(i, 1);
|
||||
i -= 1;
|
||||
} else if (part === "..") {
|
||||
if (i === 1 && (name[2] === '..' || name[0] === '..')) {
|
||||
//End of the line. Keep at least one non-dot
|
||||
//path segment at the front so it can be mapped
|
||||
//correctly to disk. Otherwise, there is likely
|
||||
//no path mapping for a path starting with '..'.
|
||||
//This can still fail, but catches the most reasonable
|
||||
//uses of ..
|
||||
break;
|
||||
} else if (i > 0) {
|
||||
name.splice(i - 1, 2);
|
||||
i -= 2;
|
||||
}
|
||||
//start trimDots
|
||||
for (i = 0; i < name.length; i++) {
|
||||
part = name[i];
|
||||
if (part === '.') {
|
||||
name.splice(i, 1);
|
||||
i -= 1;
|
||||
} else if (part === '..') {
|
||||
// If at the start, or previous value is still ..,
|
||||
// keep them so that when converted to a path it may
|
||||
// still work when converted to a path, even though
|
||||
// as an ID it is less than ideal. In larger point
|
||||
// releases, may be better to just kick out an error.
|
||||
if (i === 0 || (i === 1 && name[2] === '..') || name[i - 1] === '..') {
|
||||
continue;
|
||||
} else if (i > 0) {
|
||||
name.splice(i - 1, 2);
|
||||
i -= 2;
|
||||
}
|
||||
}
|
||||
//end trimDots
|
||||
|
||||
name = name.join("/");
|
||||
} else if (name.indexOf('./') === 0) {
|
||||
// No baseName, so this is ID is resolved relative
|
||||
// to baseUrl, pull off the leading dot.
|
||||
name = name.substring(2);
|
||||
}
|
||||
//end trimDots
|
||||
|
||||
name = name.join('/');
|
||||
}
|
||||
|
||||
//Apply map config if available.
|
||||
|
@ -230,32 +240,39 @@ var requirejs, require, define;
|
|||
return [prefix, name];
|
||||
}
|
||||
|
||||
//Creates a parts array for a relName where first part is plugin ID,
|
||||
//second part is resource ID. Assumes relName has already been normalized.
|
||||
function makeRelParts(relName) {
|
||||
return relName ? splitPrefix(relName) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a name map, normalizing the name, and using a plugin
|
||||
* for normalization if necessary. Grabs a ref to plugin
|
||||
* too, as an optimization.
|
||||
*/
|
||||
makeMap = function (name, relName) {
|
||||
makeMap = function (name, relParts) {
|
||||
var plugin,
|
||||
parts = splitPrefix(name),
|
||||
prefix = parts[0];
|
||||
prefix = parts[0],
|
||||
relResourceName = relParts[1];
|
||||
|
||||
name = parts[1];
|
||||
|
||||
if (prefix) {
|
||||
prefix = normalize(prefix, relName);
|
||||
prefix = normalize(prefix, relResourceName);
|
||||
plugin = callDep(prefix);
|
||||
}
|
||||
|
||||
//Normalize according
|
||||
if (prefix) {
|
||||
if (plugin && plugin.normalize) {
|
||||
name = plugin.normalize(name, makeNormalize(relName));
|
||||
name = plugin.normalize(name, makeNormalize(relResourceName));
|
||||
} else {
|
||||
name = normalize(name, relName);
|
||||
name = normalize(name, relResourceName);
|
||||
}
|
||||
} else {
|
||||
name = normalize(name, relName);
|
||||
name = normalize(name, relResourceName);
|
||||
parts = splitPrefix(name);
|
||||
prefix = parts[0];
|
||||
name = parts[1];
|
||||
|
@ -302,13 +319,14 @@ var requirejs, require, define;
|
|||
};
|
||||
|
||||
main = function (name, deps, callback, relName) {
|
||||
var cjsModule, depName, ret, map, i,
|
||||
var cjsModule, depName, ret, map, i, relParts,
|
||||
args = [],
|
||||
callbackType = typeof callback,
|
||||
usingExports;
|
||||
|
||||
//Use name if no relName
|
||||
relName = relName || name;
|
||||
relParts = makeRelParts(relName);
|
||||
|
||||
//Call the callback to define the module, if necessary.
|
||||
if (callbackType === 'undefined' || callbackType === 'function') {
|
||||
|
@ -317,7 +335,7 @@ var requirejs, require, define;
|
|||
//Default to [require, exports, module] if no deps
|
||||
deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
|
||||
for (i = 0; i < deps.length; i += 1) {
|
||||
map = makeMap(deps[i], relName);
|
||||
map = makeMap(deps[i], relParts);
|
||||
depName = map.f;
|
||||
|
||||
//Fast path CommonJS standard dependencies.
|
||||
|
@ -373,7 +391,7 @@ var requirejs, require, define;
|
|||
//deps arg is the module name, and second arg (if passed)
|
||||
//is just the relName.
|
||||
//Normalize module name, if it contains . or ..
|
||||
return callDep(makeMap(deps, callback).f);
|
||||
return callDep(makeMap(deps, makeRelParts(callback)).f);
|
||||
} else if (!deps.splice) {
|
||||
//deps is a config object, not an array.
|
||||
config = deps;
|
||||
|
@ -737,6 +755,12 @@ S2.define('select2/utils',[
|
|||
});
|
||||
};
|
||||
|
||||
Utils.entityDecode = function (html) {
|
||||
var txt = document.createElement('textarea');
|
||||
txt.innerHTML = html;
|
||||
return txt.value;
|
||||
}
|
||||
|
||||
// Append an array of jQuery nodes to a given element.
|
||||
Utils.appendMany = function ($element, $nodes) {
|
||||
// jQuery 1.7.x does not support $.fn.append() with an array
|
||||
|
@ -754,6 +778,14 @@ S2.define('select2/utils',[
|
|||
$element.append($nodes);
|
||||
};
|
||||
|
||||
// Determine whether the browser is on a touchscreen device.
|
||||
Utils.isTouchscreen = function() {
|
||||
if ('undefined' === typeof Utils._isTouchscreenCache) {
|
||||
Utils._isTouchscreenCache = 'ontouchstart' in document.documentElement;
|
||||
}
|
||||
return Utils._isTouchscreenCache;
|
||||
}
|
||||
|
||||
return Utils;
|
||||
});
|
||||
|
||||
|
@ -773,7 +805,7 @@ S2.define('select2/results',[
|
|||
|
||||
Results.prototype.render = function () {
|
||||
var $results = $(
|
||||
'<ul class="select2-results__options" role="tree"></ul>'
|
||||
'<ul class="select2-results__options" role="listbox" tabindex="-1"></ul>'
|
||||
);
|
||||
|
||||
if (this.options.get('multiple')) {
|
||||
|
@ -796,7 +828,7 @@ S2.define('select2/results',[
|
|||
this.hideLoading();
|
||||
|
||||
var $message = $(
|
||||
'<li role="treeitem" aria-live="assertive"' +
|
||||
'<li role="alert" aria-live="assertive"' +
|
||||
' class="select2-results__option"></li>'
|
||||
);
|
||||
|
||||
|
@ -858,9 +890,9 @@ S2.define('select2/results',[
|
|||
|
||||
Results.prototype.highlightFirstItem = function () {
|
||||
var $options = this.$results
|
||||
.find('.select2-results__option[aria-selected]');
|
||||
.find('.select2-results__option[data-selected]');
|
||||
|
||||
var $selected = $options.filter('[aria-selected=true]');
|
||||
var $selected = $options.filter('[data-selected=true]');
|
||||
|
||||
// Check if there are any selected options
|
||||
if ($selected.length > 0) {
|
||||
|
@ -884,7 +916,7 @@ S2.define('select2/results',[
|
|||
});
|
||||
|
||||
var $options = self.$results
|
||||
.find('.select2-results__option[aria-selected]');
|
||||
.find('.select2-results__option[data-selected]');
|
||||
|
||||
$options.each(function () {
|
||||
var $option = $(this);
|
||||
|
@ -896,9 +928,9 @@ S2.define('select2/results',[
|
|||
|
||||
if ((item.element != null && item.element.selected) ||
|
||||
(item.element == null && $.inArray(id, selectedIds) > -1)) {
|
||||
$option.attr('aria-selected', 'true');
|
||||
$option.attr('data-selected', 'true');
|
||||
} else {
|
||||
$option.attr('aria-selected', 'false');
|
||||
$option.attr('data-selected', 'false');
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -930,17 +962,18 @@ S2.define('select2/results',[
|
|||
option.className = 'select2-results__option';
|
||||
|
||||
var attrs = {
|
||||
'role': 'treeitem',
|
||||
'aria-selected': 'false'
|
||||
'role': 'option',
|
||||
'data-selected': 'false',
|
||||
'tabindex': -1
|
||||
};
|
||||
|
||||
if (data.disabled) {
|
||||
delete attrs['aria-selected'];
|
||||
delete attrs['data-selected'];
|
||||
attrs['aria-disabled'] = 'true';
|
||||
}
|
||||
|
||||
if (data.id == null) {
|
||||
delete attrs['aria-selected'];
|
||||
delete attrs['data-selected'];
|
||||
}
|
||||
|
||||
if (data._resultId != null) {
|
||||
|
@ -952,9 +985,8 @@ S2.define('select2/results',[
|
|||
}
|
||||
|
||||
if (data.children) {
|
||||
attrs.role = 'group';
|
||||
attrs['aria-label'] = data.text;
|
||||
delete attrs['aria-selected'];
|
||||
delete attrs['data-selected'];
|
||||
}
|
||||
|
||||
for (var attr in attrs) {
|
||||
|
@ -971,6 +1003,7 @@ S2.define('select2/results',[
|
|||
|
||||
var $label = $(label);
|
||||
this.template(data, label);
|
||||
$label.attr('role', 'presentation');
|
||||
|
||||
var $children = [];
|
||||
|
||||
|
@ -983,10 +1016,11 @@ S2.define('select2/results',[
|
|||
}
|
||||
|
||||
var $childrenContainer = $('<ul></ul>', {
|
||||
'class': 'select2-results__options select2-results__options--nested'
|
||||
'class': 'select2-results__options select2-results__options--nested',
|
||||
'role': 'listbox'
|
||||
});
|
||||
|
||||
$childrenContainer.append($children);
|
||||
$option.attr('role', 'list');
|
||||
|
||||
$option.append(label);
|
||||
$option.append($childrenContainer);
|
||||
|
@ -1082,7 +1116,7 @@ S2.define('select2/results',[
|
|||
|
||||
var data = $highlighted.data('data');
|
||||
|
||||
if ($highlighted.attr('aria-selected') == 'true') {
|
||||
if ($highlighted.attr('data-selected') == 'true') {
|
||||
self.trigger('close', {});
|
||||
} else {
|
||||
self.trigger('select', {
|
||||
|
@ -1094,7 +1128,7 @@ S2.define('select2/results',[
|
|||
container.on('results:previous', function () {
|
||||
var $highlighted = self.getHighlightedResults();
|
||||
|
||||
var $options = self.$results.find('[aria-selected]');
|
||||
var $options = self.$results.find('[data-selected]');
|
||||
|
||||
var currentIndex = $options.index($highlighted);
|
||||
|
||||
|
@ -1128,7 +1162,7 @@ S2.define('select2/results',[
|
|||
container.on('results:next', function () {
|
||||
var $highlighted = self.getHighlightedResults();
|
||||
|
||||
var $options = self.$results.find('[aria-selected]');
|
||||
var $options = self.$results.find('[data-selected]');
|
||||
|
||||
var currentIndex = $options.index($highlighted);
|
||||
|
||||
|
@ -1156,7 +1190,8 @@ S2.define('select2/results',[
|
|||
});
|
||||
|
||||
container.on('results:focus', function (params) {
|
||||
params.element.addClass('select2-results__option--highlighted');
|
||||
params.element.addClass('select2-results__option--highlighted').attr('aria-selected', 'true');
|
||||
self.$results.attr('aria-activedescendant', params.element.attr('id'));
|
||||
});
|
||||
|
||||
container.on('results:message', function (params) {
|
||||
|
@ -1188,13 +1223,13 @@ S2.define('select2/results',[
|
|||
});
|
||||
}
|
||||
|
||||
this.$results.on('mouseup', '.select2-results__option[aria-selected]',
|
||||
this.$results.on('mouseup', '.select2-results__option[data-selected]',
|
||||
function (evt) {
|
||||
var $this = $(this);
|
||||
|
||||
var data = $this.data('data');
|
||||
|
||||
if ($this.attr('aria-selected') === 'true') {
|
||||
if ($this.attr('data-selected') === 'true') {
|
||||
if (self.options.get('multiple')) {
|
||||
self.trigger('unselect', {
|
||||
originalEvent: evt,
|
||||
|
@ -1213,12 +1248,13 @@ S2.define('select2/results',[
|
|||
});
|
||||
});
|
||||
|
||||
this.$results.on('mouseenter', '.select2-results__option[aria-selected]',
|
||||
this.$results.on('mouseenter', '.select2-results__option[data-selected]',
|
||||
function (evt) {
|
||||
var data = $(this).data('data');
|
||||
|
||||
self.getHighlightedResults()
|
||||
.removeClass('select2-results__option--highlighted');
|
||||
.removeClass('select2-results__option--highlighted')
|
||||
.attr('aria-selected', 'false');
|
||||
|
||||
self.trigger('results:focus', {
|
||||
data: data,
|
||||
|
@ -1245,7 +1281,7 @@ S2.define('select2/results',[
|
|||
return;
|
||||
}
|
||||
|
||||
var $options = this.$results.find('[aria-selected]');
|
||||
var $options = this.$results.find('[data-selected]');
|
||||
|
||||
var currentIndex = $options.index($highlighted);
|
||||
|
||||
|
@ -1323,7 +1359,7 @@ S2.define('select2/selection/base',[
|
|||
|
||||
BaseSelection.prototype.render = function () {
|
||||
var $selection = $(
|
||||
'<span class="select2-selection" role="combobox" ' +
|
||||
'<span class="select2-selection" ' +
|
||||
' aria-haspopup="true" aria-expanded="false">' +
|
||||
'</span>'
|
||||
);
|
||||
|
@ -1349,6 +1385,7 @@ S2.define('select2/selection/base',[
|
|||
|
||||
var id = container.id + '-container';
|
||||
var resultsId = container.id + '-results';
|
||||
var searchHidden = this.options.get('minimumResultsForSearch') === Infinity;
|
||||
|
||||
this.container = container;
|
||||
|
||||
|
@ -1390,7 +1427,11 @@ S2.define('select2/selection/base',[
|
|||
self.$selection.removeAttr('aria-activedescendant');
|
||||
self.$selection.removeAttr('aria-owns');
|
||||
|
||||
self.$selection.focus();
|
||||
// This needs to be delayed as the active element is the body when the
|
||||
// key is pressed.
|
||||
window.setTimeout(function () {
|
||||
self.$selection.focus();
|
||||
}, 1);
|
||||
|
||||
self._detachCloseHandler(container);
|
||||
});
|
||||
|
@ -1440,8 +1481,14 @@ S2.define('select2/selection/base',[
|
|||
}
|
||||
|
||||
var $element = $this.data('element');
|
||||
|
||||
$element.select2('close');
|
||||
|
||||
// Remove any focus when dropdown is closed by clicking outside the select area.
|
||||
// Timeout of 1 required for close to finish wrapping up.
|
||||
setTimeout(function(){
|
||||
$this.find('*:focus').blur();
|
||||
$target.focus();
|
||||
}, 1);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
@ -1500,8 +1547,21 @@ S2.define('select2/selection/single',[
|
|||
|
||||
var id = container.id + '-container';
|
||||
|
||||
this.$selection.find('.select2-selection__rendered').attr('id', id);
|
||||
this.$selection.attr('aria-labelledby', id);
|
||||
this.$selection.find('.select2-selection__rendered')
|
||||
.attr('id', id)
|
||||
.attr('role', 'textbox')
|
||||
.attr('aria-readonly', 'true');
|
||||
|
||||
var label = this.options.get( 'label' );
|
||||
|
||||
if ( typeof( label ) === 'string' ) {
|
||||
this.$selection.attr( 'aria-label', label );
|
||||
} else {
|
||||
this.$selection.attr( 'aria-labelledby', id );
|
||||
}
|
||||
|
||||
// This makes single non-search selects work in screen readers. If it causes problems elsewhere, remove.
|
||||
this.$selection.attr('role', 'combobox');
|
||||
|
||||
this.$selection.on('mousedown', function (evt) {
|
||||
// Only respond to left clicks
|
||||
|
@ -1518,6 +1578,13 @@ S2.define('select2/selection/single',[
|
|||
// User focuses on the container
|
||||
});
|
||||
|
||||
this.$selection.on('keydown', function (evt) {
|
||||
// If user starts typing an alphanumeric key on the keyboard, open if not opened.
|
||||
if (!container.isOpen() && evt.which >= 48 && evt.which <= 90) {
|
||||
container.open();
|
||||
}
|
||||
});
|
||||
|
||||
this.$selection.on('blur', function (evt) {
|
||||
// User exits the container
|
||||
});
|
||||
|
@ -1557,9 +1624,9 @@ S2.define('select2/selection/single',[
|
|||
var selection = data[0];
|
||||
|
||||
var $rendered = this.$selection.find('.select2-selection__rendered');
|
||||
var formatted = this.display(selection, $rendered);
|
||||
var formatted = Utils.entityDecode(this.display(selection, $rendered));
|
||||
|
||||
$rendered.empty().append(formatted);
|
||||
$rendered.empty().text(formatted);
|
||||
$rendered.prop('title', selection.title || selection.text);
|
||||
};
|
||||
|
||||
|
@ -1583,7 +1650,7 @@ S2.define('select2/selection/multiple',[
|
|||
$selection.addClass('select2-selection--multiple');
|
||||
|
||||
$selection.html(
|
||||
'<ul class="select2-selection__rendered"></ul>'
|
||||
'<ul class="select2-selection__rendered" aria-live="polite" aria-relevant="additions removals" aria-atomic="true"></ul>'
|
||||
);
|
||||
|
||||
return $selection;
|
||||
|
@ -1620,6 +1687,18 @@ S2.define('select2/selection/multiple',[
|
|||
});
|
||||
}
|
||||
);
|
||||
|
||||
this.$selection.on('keydown', function (evt) {
|
||||
// If user starts typing an alphanumeric key on the keyboard, open if not opened.
|
||||
if (!container.isOpen() && evt.which >= 48 && evt.which <= 90) {
|
||||
container.open();
|
||||
}
|
||||
});
|
||||
|
||||
// Focus on the search field when the container is focused instead of the main container.
|
||||
container.on( 'focus', function(){
|
||||
self.focusOnSearch();
|
||||
});
|
||||
};
|
||||
|
||||
MultipleSelection.prototype.clear = function () {
|
||||
|
@ -1636,7 +1715,7 @@ S2.define('select2/selection/multiple',[
|
|||
MultipleSelection.prototype.selectionContainer = function () {
|
||||
var $container = $(
|
||||
'<li class="select2-selection__choice">' +
|
||||
'<span class="select2-selection__choice__remove" role="presentation">' +
|
||||
'<span class="select2-selection__choice__remove" role="presentation" aria-hidden="true">' +
|
||||
'×' +
|
||||
'</span>' +
|
||||
'</li>'
|
||||
|
@ -1645,6 +1724,24 @@ S2.define('select2/selection/multiple',[
|
|||
return $container;
|
||||
};
|
||||
|
||||
/**
|
||||
* Focus on the search field instead of the main multiselect container.
|
||||
*/
|
||||
MultipleSelection.prototype.focusOnSearch = function() {
|
||||
var self = this;
|
||||
|
||||
if ('undefined' !== typeof self.$search) {
|
||||
// Needs 1 ms delay because of other 1 ms setTimeouts when rendering.
|
||||
setTimeout(function(){
|
||||
// Prevent the dropdown opening again when focused from this.
|
||||
// This gets reset automatically when focus is triggered.
|
||||
self._keyUpPrevented = true;
|
||||
|
||||
self.$search.focus();
|
||||
}, 1);
|
||||
}
|
||||
}
|
||||
|
||||
MultipleSelection.prototype.update = function (data) {
|
||||
this.clear();
|
||||
|
||||
|
@ -1658,9 +1755,14 @@ S2.define('select2/selection/multiple',[
|
|||
var selection = data[d];
|
||||
|
||||
var $selection = this.selectionContainer();
|
||||
var removeItemTag = $selection.html();
|
||||
var formatted = this.display(selection, $selection);
|
||||
if ('string' === typeof formatted) {
|
||||
formatted = Utils.entityDecode(formatted.trim());
|
||||
}
|
||||
|
||||
$selection.append(formatted);
|
||||
$selection.text(formatted);
|
||||
$selection.prepend(removeItemTag);
|
||||
$selection.prop('title', selection.title || selection.text);
|
||||
|
||||
$selection.data('data', selection);
|
||||
|
@ -1699,7 +1801,7 @@ S2.define('select2/selection/placeholder',[
|
|||
Placeholder.prototype.createPlaceholder = function (decorated, placeholder) {
|
||||
var $placeholder = this.selectionContainer();
|
||||
|
||||
$placeholder.html(this.display(placeholder));
|
||||
$placeholder.text(Utils.entityDecode(this.display(placeholder)));
|
||||
$placeholder.addClass('select2-selection__placeholder')
|
||||
.removeClass('select2-selection__choice');
|
||||
|
||||
|
@ -1836,8 +1938,8 @@ S2.define('select2/selection/search',[
|
|||
Search.prototype.render = function (decorated) {
|
||||
var $search = $(
|
||||
'<li class="select2-search select2-search--inline">' +
|
||||
'<input class="select2-search__field" type="search" tabindex="-1"' +
|
||||
' autocomplete="off" autocorrect="off" autocapitalize="off"' +
|
||||
'<input class="select2-search__field" type="text" tabindex="-1"' +
|
||||
' autocomplete="off" autocorrect="off" autocapitalize="none"' +
|
||||
' spellcheck="false" role="textbox" aria-autocomplete="list" />' +
|
||||
'</li>'
|
||||
);
|
||||
|
@ -1854,16 +1956,19 @@ S2.define('select2/selection/search',[
|
|||
|
||||
Search.prototype.bind = function (decorated, container, $container) {
|
||||
var self = this;
|
||||
var resultsId = container.id + '-results';
|
||||
|
||||
decorated.call(this, container, $container);
|
||||
|
||||
container.on('open', function () {
|
||||
self.$search.attr('aria-owns', resultsId);
|
||||
self.$search.trigger('focus');
|
||||
});
|
||||
|
||||
container.on('close', function () {
|
||||
self.$search.val('');
|
||||
self.$search.removeAttr('aria-activedescendant');
|
||||
self.$search.removeAttr('aria-owns');
|
||||
self.$search.trigger('focus');
|
||||
});
|
||||
|
||||
|
@ -1882,7 +1987,7 @@ S2.define('select2/selection/search',[
|
|||
});
|
||||
|
||||
container.on('results:focus', function (params) {
|
||||
self.$search.attr('aria-activedescendant', params.id);
|
||||
self.$search.attr('aria-activedescendant', params.data._resultId);
|
||||
});
|
||||
|
||||
this.$selection.on('focusin', '.select2-search--inline', function (evt) {
|
||||
|
@ -1913,6 +2018,9 @@ S2.define('select2/selection/search',[
|
|||
|
||||
evt.preventDefault();
|
||||
}
|
||||
} else if (evt.which === KEYS.ENTER) {
|
||||
container.open();
|
||||
evt.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -3004,8 +3112,15 @@ S2.define('select2/data/base',[
|
|||
};
|
||||
|
||||
BaseAdapter.prototype.generateResultId = function (container, data) {
|
||||
var id = container.id + '-result-';
|
||||
var id = '';
|
||||
|
||||
if (container != null) {
|
||||
id += container.id
|
||||
} else {
|
||||
id += Utils.generateChars(4);
|
||||
}
|
||||
|
||||
id += '-result-';
|
||||
id += Utils.generateChars(4);
|
||||
|
||||
if (data.id != null) {
|
||||
|
@ -3191,7 +3306,7 @@ S2.define('select2/data/select',[
|
|||
}
|
||||
}
|
||||
|
||||
if (data.id) {
|
||||
if (data.id !== undefined) {
|
||||
option.value = data.id;
|
||||
}
|
||||
|
||||
|
@ -3289,7 +3404,7 @@ S2.define('select2/data/select',[
|
|||
item.text = item.text.toString();
|
||||
}
|
||||
|
||||
if (item._resultId == null && item.id && this.container != null) {
|
||||
if (item._resultId == null && item.id) {
|
||||
item._resultId = this.generateResultId(this.container, item);
|
||||
}
|
||||
|
||||
|
@ -3466,6 +3581,7 @@ S2.define('select2/data/ajax',[
|
|||
}
|
||||
|
||||
callback(results);
|
||||
self.container.focusOnActiveElement();
|
||||
}, function () {
|
||||
// Attempt to detect if a request was aborted
|
||||
// Only works if the transport exposes a status property
|
||||
|
@ -3550,7 +3666,10 @@ S2.define('select2/data/tags',[
|
|||
}, true)
|
||||
);
|
||||
|
||||
var checkText = option.text === params.term;
|
||||
var optionText = (option.text || '').toUpperCase();
|
||||
var paramsTerm = (params.term || '').toUpperCase();
|
||||
|
||||
var checkText = optionText === paramsTerm;
|
||||
|
||||
if (checkText || checkChildren) {
|
||||
if (child) {
|
||||
|
@ -3887,9 +4006,9 @@ S2.define('select2/dropdown/search',[
|
|||
|
||||
var $search = $(
|
||||
'<span class="select2-search select2-search--dropdown">' +
|
||||
'<input class="select2-search__field" type="search" tabindex="-1"' +
|
||||
' autocomplete="off" autocorrect="off" autocapitalize="off"' +
|
||||
' spellcheck="false" role="textbox" />' +
|
||||
'<input class="select2-search__field" type="text" tabindex="-1"' +
|
||||
' autocomplete="off" autocorrect="off" autocapitalize="none"' +
|
||||
' spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" />' +
|
||||
'</span>'
|
||||
);
|
||||
|
||||
|
@ -3903,6 +4022,7 @@ S2.define('select2/dropdown/search',[
|
|||
|
||||
Search.prototype.bind = function (decorated, container, $container) {
|
||||
var self = this;
|
||||
var resultsId = container.id + '-results';
|
||||
|
||||
decorated.call(this, container, $container);
|
||||
|
||||
|
@ -3926,7 +4046,7 @@ S2.define('select2/dropdown/search',[
|
|||
|
||||
container.on('open', function () {
|
||||
self.$search.attr('tabindex', 0);
|
||||
|
||||
self.$search.attr('aria-owns', resultsId);
|
||||
self.$search.focus();
|
||||
|
||||
window.setTimeout(function () {
|
||||
|
@ -3936,12 +4056,13 @@ S2.define('select2/dropdown/search',[
|
|||
|
||||
container.on('close', function () {
|
||||
self.$search.attr('tabindex', -1);
|
||||
|
||||
self.$search.removeAttr('aria-activedescendant');
|
||||
self.$search.removeAttr('aria-owns');
|
||||
self.$search.val('');
|
||||
});
|
||||
|
||||
container.on('focus', function () {
|
||||
if (container.isOpen()) {
|
||||
if (!container.isOpen()) {
|
||||
self.$search.focus();
|
||||
}
|
||||
});
|
||||
|
@ -3957,6 +4078,10 @@ S2.define('select2/dropdown/search',[
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
container.on('results:focus', function (params) {
|
||||
self.$search.attr('aria-activedescendant', params.data._resultId);
|
||||
});
|
||||
};
|
||||
|
||||
Search.prototype.handleSearch = function (evt) {
|
||||
|
@ -4098,7 +4223,7 @@ S2.define('select2/dropdown/infiniteScroll',[
|
|||
var $option = $(
|
||||
'<li ' +
|
||||
'class="select2-results__option select2-results__option--load-more"' +
|
||||
'role="treeitem" aria-disabled="true"></li>'
|
||||
'role="option" aria-disabled="true"></li>'
|
||||
);
|
||||
|
||||
var message = this.options.get('translations').get('loadingMore');
|
||||
|
@ -5344,16 +5469,22 @@ S2.define('select2/core',[
|
|||
});
|
||||
});
|
||||
|
||||
this.on('keypress', function (evt) {
|
||||
var key = evt.which;
|
||||
this.on('open', function(){
|
||||
// Focus on the active element when opening dropdown.
|
||||
// Needs 1 ms delay because of other 1 ms setTimeouts when rendering.
|
||||
setTimeout(function(){
|
||||
self.focusOnActiveElement();
|
||||
}, 1);
|
||||
});
|
||||
|
||||
$(document).on('keydown', function (evt) {
|
||||
var key = evt.which;
|
||||
if (self.isOpen()) {
|
||||
if (key === KEYS.ESC || key === KEYS.TAB ||
|
||||
(key === KEYS.UP && evt.altKey)) {
|
||||
if (key === KEYS.ESC || (key === KEYS.UP && evt.altKey)) {
|
||||
self.close();
|
||||
|
||||
evt.preventDefault();
|
||||
} else if (key === KEYS.ENTER) {
|
||||
} else if (key === KEYS.ENTER || key === KEYS.TAB) {
|
||||
self.trigger('results:select', {});
|
||||
|
||||
evt.preventDefault();
|
||||
|
@ -5370,17 +5501,42 @@ S2.define('select2/core',[
|
|||
|
||||
evt.preventDefault();
|
||||
}
|
||||
} else {
|
||||
if (key === KEYS.ENTER || key === KEYS.SPACE ||
|
||||
(key === KEYS.DOWN && evt.altKey)) {
|
||||
self.open();
|
||||
|
||||
var $searchField = self.$dropdown.find('.select2-search__field');
|
||||
if (! $searchField.length) {
|
||||
$searchField = self.$container.find('.select2-search__field');
|
||||
}
|
||||
|
||||
// Move the focus to the selected element on keyboard navigation.
|
||||
// Required for screen readers to work properly.
|
||||
if (key === KEYS.DOWN || key === KEYS.UP) {
|
||||
self.focusOnActiveElement();
|
||||
} else {
|
||||
// Focus on the search if user starts typing.
|
||||
$searchField.focus();
|
||||
// Focus back to active selection when finished typing.
|
||||
// Small delay so typed character can be read by screen reader.
|
||||
setTimeout(function(){
|
||||
self.focusOnActiveElement();
|
||||
}, 1000);
|
||||
}
|
||||
} else if (self.hasFocus()) {
|
||||
if (key === KEYS.ENTER || key === KEYS.SPACE ||
|
||||
key === KEYS.DOWN) {
|
||||
self.open();
|
||||
evt.preventDefault();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Select2.prototype.focusOnActiveElement = function () {
|
||||
// Don't mess with the focus on touchscreens because it causes havoc with on-screen keyboards.
|
||||
if (this.isOpen() && ! Utils.isTouchscreen()) {
|
||||
this.$results.find('li.select2-results__option--highlighted').focus();
|
||||
}
|
||||
};
|
||||
|
||||
Select2.prototype._syncAttributes = function () {
|
||||
this.options.set('disabled', this.$element.prop('disabled'));
|
||||
|
||||
|
@ -5653,11 +5809,11 @@ S2.define('jquery.select2',[
|
|||
'./select2/core',
|
||||
'./select2/defaults'
|
||||
], function ($, _, Select2, Defaults) {
|
||||
if ($.fn.select2 == null) {
|
||||
if ($.fn.selectWoo == null) {
|
||||
// All methods that should return the element
|
||||
var thisMethods = ['open', 'close', 'destroy'];
|
||||
|
||||
$.fn.select2 = function (options) {
|
||||
$.fn.selectWoo = function (options) {
|
||||
options = options || {};
|
||||
|
||||
if (typeof options === 'object') {
|
||||
|
@ -5697,10 +5853,17 @@ S2.define('jquery.select2',[
|
|||
};
|
||||
}
|
||||
|
||||
if ($.fn.select2.defaults == null) {
|
||||
$.fn.select2.defaults = Defaults;
|
||||
if ($.fn.select2 != null && $.fn.select2.defaults != null) {
|
||||
$.fn.selectWoo.defaults = $.fn.select2.defaults;
|
||||
}
|
||||
|
||||
if ($.fn.selectWoo.defaults == null) {
|
||||
$.fn.selectWoo.defaults = Defaults;
|
||||
}
|
||||
|
||||
// Also register selectWoo under select2 if select2 is not already present.
|
||||
$.fn.select2 = $.fn.select2 || $.fn.selectWoo;
|
||||
|
||||
return Select2;
|
||||
});
|
||||
|
||||
|
@ -5719,6 +5882,7 @@ S2.define('jquery.select2',[
|
|||
// This allows Select2 to use the internal loader outside of this file, such
|
||||
// as in the language files.
|
||||
jQuery.fn.select2.amd = S2;
|
||||
jQuery.fn.selectWoo.amd = S2;
|
||||
|
||||
// Return the Select2 instance for anyone who is importing it.
|
||||
return select2;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*!
|
||||
* SelectWoo 1.0.6
|
||||
* SelectWoo 1.0.9
|
||||
* https://github.com/woocommerce/selectWoo
|
||||
*
|
||||
* Released under the MIT license
|
||||
|
@ -755,8 +755,8 @@ S2.define('select2/utils',[
|
|||
});
|
||||
};
|
||||
|
||||
Utils.entityDecode = function(html) {
|
||||
var txt = document.createElement("textarea");
|
||||
Utils.entityDecode = function (html) {
|
||||
var txt = document.createElement('textarea');
|
||||
txt.innerHTML = html;
|
||||
return txt.value;
|
||||
}
|
||||
|
@ -1551,7 +1551,14 @@ S2.define('select2/selection/single',[
|
|||
.attr('id', id)
|
||||
.attr('role', 'textbox')
|
||||
.attr('aria-readonly', 'true');
|
||||
this.$selection.attr('aria-labelledby', id);
|
||||
|
||||
var label = this.options.get( 'label' );
|
||||
|
||||
if ( typeof( label ) === 'string' ) {
|
||||
this.$selection.attr( 'aria-label', label );
|
||||
} else {
|
||||
this.$selection.attr( 'aria-labelledby', id );
|
||||
}
|
||||
|
||||
// This makes single non-search selects work in screen readers. If it causes problems elsewhere, remove.
|
||||
this.$selection.attr('role', 'combobox');
|
||||
|
@ -4398,6 +4405,7 @@ S2.define('select2/dropdown/attachBody',[
|
|||
|
||||
var parentOffset = $offsetParent.offset();
|
||||
|
||||
css.top -= parentOffset.top;
|
||||
css.left -= parentOffset.left;
|
||||
|
||||
if (!isCurrentlyAbove && !isCurrentlyBelow) {
|
||||
|
@ -4412,7 +4420,7 @@ S2.define('select2/dropdown/attachBody',[
|
|||
|
||||
if (newDirection == 'above' ||
|
||||
(isCurrentlyAbove && newDirection !== 'below')) {
|
||||
css.top = container.top - dropdown.height;
|
||||
css.top = container.top - parentOffset.top - dropdown.height;
|
||||
}
|
||||
|
||||
if (newDirection != null) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*!
|
||||
* SelectWoo 1.0.6
|
||||
* SelectWoo 1.0.9
|
||||
* https://github.com/woocommerce/selectWoo
|
||||
*
|
||||
* Released under the MIT license
|
||||
|
@ -755,8 +755,8 @@ S2.define('select2/utils',[
|
|||
});
|
||||
};
|
||||
|
||||
Utils.entityDecode = function(html) {
|
||||
var txt = document.createElement("textarea");
|
||||
Utils.entityDecode = function (html) {
|
||||
var txt = document.createElement('textarea');
|
||||
txt.innerHTML = html;
|
||||
return txt.value;
|
||||
}
|
||||
|
@ -1551,7 +1551,14 @@ S2.define('select2/selection/single',[
|
|||
.attr('id', id)
|
||||
.attr('role', 'textbox')
|
||||
.attr('aria-readonly', 'true');
|
||||
this.$selection.attr('aria-labelledby', id);
|
||||
|
||||
var label = this.options.get( 'label' );
|
||||
|
||||
if ( typeof( label ) === 'string' ) {
|
||||
this.$selection.attr( 'aria-label', label );
|
||||
} else {
|
||||
this.$selection.attr( 'aria-labelledby', id );
|
||||
}
|
||||
|
||||
// This makes single non-search selects work in screen readers. If it causes problems elsewhere, remove.
|
||||
this.$selection.attr('role', 'combobox');
|
||||
|
|
|
@ -1,5 +1,21 @@
|
|||
== Changelog ==
|
||||
|
||||
= 5.2.2 2021-04-15 =
|
||||
|
||||
**WooCommerce**
|
||||
|
||||
* Fix - Can't grant permission for download from order details page. #29691
|
||||
|
||||
= 5.2.1 2021-04-14 =
|
||||
|
||||
**WooCommerce**
|
||||
|
||||
* Update - WooCommerce Blocks package 4.7.2. #29660
|
||||
|
||||
**WooCommerce Blocks - 4.7.2**
|
||||
|
||||
* Fix - Check if Cart and Checkout are registered before removing payment methods. ([4056](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/4056))
|
||||
|
||||
= 5.2.0 2021-04-13 =
|
||||
|
||||
**WooCommerce**
|
||||
|
|
|
@ -21,8 +21,8 @@
|
|||
"pelago/emogrifier": "3.1.0",
|
||||
"psr/container": "1.0.0",
|
||||
"woocommerce/action-scheduler": "3.1.6",
|
||||
"woocommerce/woocommerce-admin": "2.1.5",
|
||||
"woocommerce/woocommerce-blocks": "4.7.1"
|
||||
"woocommerce/woocommerce-admin": "2.2.2",
|
||||
"woocommerce/woocommerce-blocks": "4.9.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.4"
|
||||
|
|
|
@ -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": "5005eed74baf10300e2895e9b6a4f344",
|
||||
"content-hash": "a604678b268820c78736d599e1eb6726",
|
||||
"packages": [
|
||||
{
|
||||
"name": "automattic/jetpack-autoloader",
|
||||
|
@ -51,9 +51,6 @@
|
|||
"GPL-2.0-or-later"
|
||||
],
|
||||
"description": "Creates a custom autoloader for a plugin or theme.",
|
||||
"support": {
|
||||
"source": "https://github.com/Automattic/jetpack-autoloader/tree/2.10.1"
|
||||
},
|
||||
"time": "2021-03-30T15:15:59+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -85,23 +82,20 @@
|
|||
"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"
|
||||
},
|
||||
{
|
||||
"name": "composer/installers",
|
||||
"version": "v1.10.0",
|
||||
"version": "v1.11.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/composer/installers.git",
|
||||
"reference": "1a0357fccad9d1cc1ea0c9a05b8847fbccccb78d"
|
||||
"reference": "ae03311f45dfe194412081526be2e003960df74b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/composer/installers/zipball/1a0357fccad9d1cc1ea0c9a05b8847fbccccb78d",
|
||||
"reference": "1a0357fccad9d1cc1ea0c9a05b8847fbccccb78d",
|
||||
"url": "https://api.github.com/repos/composer/installers/zipball/ae03311f45dfe194412081526be2e003960df74b",
|
||||
"reference": "ae03311f45dfe194412081526be2e003960df74b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -195,6 +189,7 @@
|
|||
"majima",
|
||||
"mako",
|
||||
"mediawiki",
|
||||
"miaoxing",
|
||||
"modulework",
|
||||
"modx",
|
||||
"moodle",
|
||||
|
@ -212,16 +207,13 @@
|
|||
"sydes",
|
||||
"sylius",
|
||||
"symfony",
|
||||
"tastyigniter",
|
||||
"typo3",
|
||||
"wordpress",
|
||||
"yawik",
|
||||
"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",
|
||||
|
@ -236,7 +228,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-01-14T11:07:16+00:00"
|
||||
"time": "2021-04-28T06:42:17+00:00"
|
||||
},
|
||||
{
|
||||
"name": "maxmind-db/reader",
|
||||
|
@ -296,10 +288,6 @@
|
|||
"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"
|
||||
},
|
||||
{
|
||||
|
@ -374,10 +362,6 @@
|
|||
"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"
|
||||
},
|
||||
{
|
||||
|
@ -427,10 +411,6 @@
|
|||
"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"
|
||||
},
|
||||
{
|
||||
|
@ -484,9 +464,6 @@
|
|||
],
|
||||
"description": "Symfony CssSelector Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/css-selector/tree/master"
|
||||
},
|
||||
"time": "2017-05-01T15:01:29+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -522,24 +499,20 @@
|
|||
],
|
||||
"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.1.5",
|
||||
"version": "2.2.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/woocommerce/woocommerce-admin.git",
|
||||
"reference": "71c233d8463f551430edbe1877e51d19a4bb2df6"
|
||||
"reference": "161e6afa01a3fb69533cfa2b245a71df7512ec3f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/71c233d8463f551430edbe1877e51d19a4bb2df6",
|
||||
"reference": "71c233d8463f551430edbe1877e51d19a4bb2df6",
|
||||
"url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/161e6afa01a3fb69533cfa2b245a71df7512ec3f",
|
||||
"reference": "161e6afa01a3fb69533cfa2b245a71df7512ec3f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -571,24 +544,20 @@
|
|||
],
|
||||
"description": "A modern, javascript-driven WooCommerce Admin experience.",
|
||||
"homepage": "https://github.com/woocommerce/woocommerce-admin",
|
||||
"support": {
|
||||
"issues": "https://github.com/woocommerce/woocommerce-admin/issues",
|
||||
"source": "https://github.com/woocommerce/woocommerce-admin/tree/v2.1.5"
|
||||
},
|
||||
"time": "2021-04-02T16:48:38+00:00"
|
||||
"time": "2021-04-29T14:11:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "woocommerce/woocommerce-blocks",
|
||||
"version": "v4.7.1",
|
||||
"version": "v4.9.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/woocommerce/woocommerce-gutenberg-products-block.git",
|
||||
"reference": "841c49b8626f4eb717056a2d1e3eba6140f45e2c"
|
||||
"reference": "62f32bfb45dfcb2ba3ca349a6ed0a8cf48ddefce"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/woocommerce/woocommerce-gutenberg-products-block/zipball/841c49b8626f4eb717056a2d1e3eba6140f45e2c",
|
||||
"reference": "841c49b8626f4eb717056a2d1e3eba6140f45e2c",
|
||||
"url": "https://api.github.com/repos/woocommerce/woocommerce-gutenberg-products-block/zipball/62f32bfb45dfcb2ba3ca349a6ed0a8cf48ddefce",
|
||||
"reference": "62f32bfb45dfcb2ba3ca349a6ed0a8cf48ddefce",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -622,11 +591,7 @@
|
|||
"gutenberg",
|
||||
"woocommerce"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues",
|
||||
"source": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/tree/v4.7.1"
|
||||
},
|
||||
"time": "2021-04-02T11:18:50+00:00"
|
||||
"time": "2021-04-13T16:11:16+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [
|
||||
|
@ -674,10 +639,6 @@
|
|||
"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"
|
||||
}
|
||||
],
|
||||
|
@ -693,5 +654,5 @@
|
|||
"platform-overrides": {
|
||||
"php": "7.0"
|
||||
},
|
||||
"plugin-api-version": "2.0.0"
|
||||
"plugin-api-version": "1.1.0"
|
||||
}
|
||||
|
|
|
@ -534,7 +534,7 @@ return array(
|
|||
'SO' => __( 'Sololá', 'woocommerce' ),
|
||||
'SU' => __( 'Suchitepéquez', 'woocommerce' ),
|
||||
'TO' => __( 'Totonicapán', 'woocommerce' ),
|
||||
'ZA' => __( 'Zacapa', 'woocommerce' ),
|
||||
'ZA' => __( 'Zacapa', 'woocommerce' )
|
||||
),
|
||||
'HK' => array( // Hong Kong states.
|
||||
'HONG KONG' => __( 'Hong Kong Island', 'woocommerce' ),
|
||||
|
@ -1296,28 +1296,7 @@ return array(
|
|||
),
|
||||
'PL' => array(),
|
||||
'PR' => array(),
|
||||
'PT' => array( // Portugal states. Ref: https://github.com/unicode-org/cldr/blob/release-38-1/common/subdivisions/en.xml#L4139-L4159
|
||||
'PT-01' => __( 'Aveiro', 'woocommerce' ),
|
||||
'PT-02' => __( 'Beja', 'woocommerce' ),
|
||||
'PT-03' => __( 'Braga', 'woocommerce' ),
|
||||
'PT-04' => __( 'Bragança', 'woocommerce' ),
|
||||
'PT-05' => __( 'Castelo Branco', 'woocommerce' ),
|
||||
'PT-06' => __( 'Coimbra', 'woocommerce' ),
|
||||
'PT-07' => __( 'Évora', 'woocommerce' ),
|
||||
'PT-08' => __( 'Faro', 'woocommerce' ),
|
||||
'PT-09' => __( 'Guarda', 'woocommerce' ),
|
||||
'PT-10' => __( 'Leiria', 'woocommerce' ),
|
||||
'PT-11' => __( 'Lisbon', 'woocommerce' ),
|
||||
'PT-12' => __( 'Portalegre', 'woocommerce' ),
|
||||
'PT-13' => __( 'Porto', 'woocommerce' ),
|
||||
'PT-14' => __( 'Santarém', 'woocommerce' ),
|
||||
'PT-15' => __( 'Setúbal', 'woocommerce' ),
|
||||
'PT-16' => __( 'Viana do Castelo', 'woocommerce' ),
|
||||
'PT-17' => __( 'Vila Real', 'woocommerce' ),
|
||||
'PT-18' => __( 'Viseu', 'woocommerce' ),
|
||||
'PT-20' => __( 'Azores', 'woocommerce' ),
|
||||
'PT-30' => __( 'Madeira', 'woocommerce' ),
|
||||
),
|
||||
'PT' => array(),
|
||||
'PY' => array( // Paraguay states.
|
||||
'PY-ASU' => __( 'Asunción', 'woocommerce' ),
|
||||
'PY-1' => __( 'Concepción', 'woocommerce' ),
|
||||
|
|
|
@ -172,7 +172,7 @@ if ( ! class_exists( 'WC_Admin_Dashboard_Setup', false ) ) :
|
|||
*/
|
||||
private function populate_payment_tasks() {
|
||||
$is_woo_payment_installed = is_plugin_active( 'woocommerce-payments/woocommerce-payments.php' );
|
||||
$country = explode( ':', get_option( 'woocommerce_default_country', '' ) )[0];
|
||||
$country = explode( ':', get_option( 'woocommerce_default_country', 'US:CA' ) )[0];
|
||||
|
||||
// woocommerce-payments requires its plugin activated and country must be US.
|
||||
if ( ! $is_woo_payment_installed || 'US' !== $country ) {
|
||||
|
|
|
@ -430,7 +430,10 @@ class WC_Admin_Post_Types {
|
|||
// phpcs:enable WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
|
||||
$product->set_manage_stock( $manage_stock );
|
||||
$product->set_backorders( $backorders );
|
||||
|
||||
if ( 'external' !== $product->get_type() ) {
|
||||
$product->set_backorders( $backorders );
|
||||
}
|
||||
|
||||
if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) {
|
||||
$stock_amount = 'yes' === $manage_stock && isset( $request_data['_stock'] ) && is_numeric( wp_unslash( $request_data['_stock'] ) ) ? wc_stock_amount( wp_unslash( $request_data['_stock'] ) ) : '';
|
||||
|
@ -550,7 +553,10 @@ class WC_Admin_Post_Types {
|
|||
$stock_amount = 'yes' === $manage_stock && ! empty( $request_data['change_stock'] ) && isset( $request_data['_stock'] ) ? wc_stock_amount( $request_data['_stock'] ) : $product->get_stock_quantity();
|
||||
|
||||
$product->set_manage_stock( $manage_stock );
|
||||
$product->set_backorders( $backorders );
|
||||
|
||||
if ( 'external' !== $product->get_type() ) {
|
||||
$product->set_backorders( $backorders );
|
||||
}
|
||||
|
||||
if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) {
|
||||
$change_stock = absint( $request_data['change_stock'] );
|
||||
|
|
|
@ -481,7 +481,7 @@ class WC_Admin_Setup_Wizard {
|
|||
$state = WC()->countries->get_base_state();
|
||||
$country = WC()->countries->get_base_country();
|
||||
$postcode = WC()->countries->get_base_postcode();
|
||||
$currency = get_option( 'woocommerce_currency', 'GBP' );
|
||||
$currency = get_option( 'woocommerce_currency', 'USD' );
|
||||
$product_type = get_option( 'woocommerce_product_type', 'both' );
|
||||
$sell_in_person = get_option( 'woocommerce_sell_in_person', 'none_selected' );
|
||||
|
||||
|
|
|
@ -11,6 +11,8 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use Automattic\WooCommerce\Internal\AssignDefaultCategory;
|
||||
|
||||
/**
|
||||
* WC_Admin_Taxonomies class.
|
||||
*/
|
||||
|
@ -49,6 +51,12 @@ class WC_Admin_Taxonomies {
|
|||
|
||||
// Category/term ordering.
|
||||
add_action( 'create_term', array( $this, 'create_term' ), 5, 3 );
|
||||
add_action(
|
||||
'delete_product_cat',
|
||||
function() {
|
||||
wc_get_container()->get( AssignDefaultCategory::class )->schedule_action();
|
||||
}
|
||||
);
|
||||
|
||||
// Add form.
|
||||
add_action( 'product_cat_add_form_fields', array( $this, 'add_category_fields' ) );
|
||||
|
@ -91,7 +99,7 @@ class WC_Admin_Taxonomies {
|
|||
* @param string $taxonomy Taxonomy slug.
|
||||
*/
|
||||
public function create_term( $term_id, $tt_id = '', $taxonomy = '' ) {
|
||||
if ( 'product_cat' != $taxonomy && ! taxonomy_is_product_attribute( $taxonomy ) ) {
|
||||
if ( 'product_cat' !== $taxonomy && ! taxonomy_is_product_attribute( $taxonomy ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -45,12 +45,19 @@ class WC_Settings_Emails extends WC_Settings_Page {
|
|||
* @return array
|
||||
*/
|
||||
protected function get_settings_for_default_section() {
|
||||
|
||||
$settings =
|
||||
$desc_help_text = sprintf(
|
||||
/* translators: %1$s: Link to WP Mail Logging plugin, %2$s: Link to Email FAQ support page. */
|
||||
__( 'To ensure your store’s notifications arrive in your and your customers’ inboxes, we recommend connecting your email address to your domain and setting up a dedicated SMTP server. If something doesn’t seem to be sending correctly, install the <a href="%1$s">WP Mail Logging Plugin</a> or check the <a href="%2$s">Email FAQ page</a>.', 'woocommerce' ),
|
||||
'https://wordpress.org/plugins/wp-mail-logging/',
|
||||
'https://docs.woocommerce.com/document/email-faq'
|
||||
);
|
||||
$settings = apply_filters(
|
||||
'woocommerce_email_settings',
|
||||
array(
|
||||
array(
|
||||
'title' => __( 'Email notifications', 'woocommerce' ),
|
||||
'desc' => __( 'Email notifications sent from WooCommerce are listed below. Click on an email to configure it.', 'woocommerce' ),
|
||||
/* translators: %s: help description with link to WP Mail logging and support page. */
|
||||
'desc' => sprintf( __( 'Email notifications sent from WooCommerce are listed below. Click on an email to configure it.<br>%s', 'woocommerce' ), $desc_help_text ),
|
||||
'type' => 'title',
|
||||
'id' => 'email_notification_settings',
|
||||
),
|
||||
|
|
|
@ -80,7 +80,7 @@ class WC_Settings_General extends WC_Settings_Page {
|
|||
'title' => __( 'Country / State', 'woocommerce' ),
|
||||
'desc' => __( 'The country and state or province, if any, in which your business is located.', 'woocommerce' ),
|
||||
'id' => 'woocommerce_default_country',
|
||||
'default' => 'GB',
|
||||
'default' => 'US:CA',
|
||||
'type' => 'single_select_country',
|
||||
'desc_tip' => true,
|
||||
),
|
||||
|
@ -228,7 +228,7 @@ class WC_Settings_General extends WC_Settings_Page {
|
|||
'title' => __( 'Currency', 'woocommerce' ),
|
||||
'desc' => __( 'This controls what currency prices are listed at in the catalog and which currency gateways will take payments in.', 'woocommerce' ),
|
||||
'id' => 'woocommerce_currency',
|
||||
'default' => 'GBP',
|
||||
'default' => 'USD',
|
||||
'type' => 'select',
|
||||
'class' => 'wc-enhanced-select',
|
||||
'desc_tip' => true,
|
||||
|
|
|
@ -56,8 +56,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
value="<?php echo esc_attr( isset( $_GET['search'] ) ? sanitize_text_field( wp_unslash( $_GET['search'] ) ) : '' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended ?>"
|
||||
placeholder="<?php esc_attr_e( 'Enter a search term and press enter', 'woocommerce' ); ?>">
|
||||
<input type="hidden" name="page" value="wc-addons">
|
||||
<?php $page_section = ( isset( $_GET['section'] ) && '_featured' !== $_GET['section'] ) ? sanitize_text_field( wp_unslash( $_GET['section'] ) ) : '_all'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended ?>
|
||||
<input type="hidden" name="section" value="<?php echo esc_attr( $page_section ); ?>">
|
||||
<input type="hidden" name="section" value="_all">
|
||||
</form>
|
||||
<?php if ( '_featured' === $current_section ) : ?>
|
||||
<div class="addons-featured">
|
||||
|
|
|
@ -224,7 +224,7 @@ class WC_Frontend_Scripts {
|
|||
'selectWoo' => array(
|
||||
'src' => self::get_asset_url( 'assets/js/selectWoo/selectWoo.full' . $suffix . '.js' ),
|
||||
'deps' => array( 'jquery' ),
|
||||
'version' => '1.0.6',
|
||||
'version' => '1.0.9',
|
||||
),
|
||||
'wc-address-i18n' => array(
|
||||
'src' => self::get_asset_url( 'assets/js/frontend/address-i18n' . $suffix . '.js' ),
|
||||
|
|
|
@ -139,11 +139,23 @@ class WC_Logger implements WC_Logger_Interface {
|
|||
}
|
||||
|
||||
if ( $this->should_handle( $level ) ) {
|
||||
$timestamp = current_time( 'timestamp', 1 );
|
||||
$message = apply_filters( 'woocommerce_logger_log_message', $message, $level, $context );
|
||||
$timestamp = time();
|
||||
|
||||
foreach ( $this->handlers as $handler ) {
|
||||
$handler->handle( $timestamp, $level, $message, $context );
|
||||
/**
|
||||
* Filter the logging message. Returning null will prevent logging from occuring since 5.3.
|
||||
*
|
||||
* @since 3.1
|
||||
* @param string $message Log message.
|
||||
* @param string $level One of: emergency, alert, critical, error, warning, notice, info, or debug.
|
||||
* @param array $context Additional information for log handlers.
|
||||
* @param object $handler The handler object, such as WC_Log_Handler_File. Available since 5.3.
|
||||
*/
|
||||
$message = apply_filters( 'woocommerce_logger_log_message', $message, $level, $context, $handler );
|
||||
|
||||
if ( null !== $message ) {
|
||||
$handler->handle( $timestamp, $level, $message, $context );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Internal\DownloadPermissionsAdjuster;
|
||||
use Automattic\WooCommerce\Internal\AssignDefaultCategory;
|
||||
use Automattic\WooCommerce\Proxies\LegacyProxy;
|
||||
|
||||
/**
|
||||
|
@ -207,6 +208,7 @@ final class WooCommerce {
|
|||
|
||||
// These classes set up hooks on instantiation.
|
||||
wc_get_container()->get( DownloadPermissionsAdjuster::class );
|
||||
wc_get_container()->get( AssignDefaultCategory::class );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
* @package WooCommerce
|
||||
*/
|
||||
|
||||
use Automattic\WooCommerce\Internal\ThemeSupport;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
|
@ -13,10 +15,19 @@ defined( 'ABSPATH' ) || exit;
|
|||
*/
|
||||
class WC_Shop_Customizer {
|
||||
|
||||
/**
|
||||
* Holds the instance of ThemeSupport to use.
|
||||
*
|
||||
* @var ThemeSupport $theme_support The instance of ThemeSupport to use.
|
||||
*/
|
||||
private $theme_support;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->theme_support = wc_get_container()->get( ThemeSupport::class );
|
||||
|
||||
add_action( 'customize_register', array( $this, 'add_sections' ) );
|
||||
add_action( 'customize_controls_print_styles', array( $this, 'add_styles' ) );
|
||||
add_action( 'customize_controls_print_scripts', array( $this, 'add_scripts' ), 30 );
|
||||
|
@ -545,11 +556,11 @@ class WC_Shop_Customizer {
|
|||
)
|
||||
);
|
||||
|
||||
if ( ! wc_get_theme_support( 'single_image_width' ) ) {
|
||||
if ( ! $this->theme_support->has_option( 'single_image_width', false ) ) {
|
||||
$wp_customize->add_setting(
|
||||
'woocommerce_single_image_width',
|
||||
array(
|
||||
'default' => 600,
|
||||
'default' => $this->theme_support->get_option( 'single_image_width', 600 ),
|
||||
'type' => 'option',
|
||||
'capability' => 'manage_woocommerce',
|
||||
'sanitize_callback' => 'absint',
|
||||
|
@ -573,11 +584,11 @@ class WC_Shop_Customizer {
|
|||
);
|
||||
}
|
||||
|
||||
if ( ! wc_get_theme_support( 'thumbnail_image_width' ) ) {
|
||||
if ( ! $this->theme_support->has_option( 'thumbnail_image_width', false ) ) {
|
||||
$wp_customize->add_setting(
|
||||
'woocommerce_thumbnail_image_width',
|
||||
array(
|
||||
'default' => 300,
|
||||
'default' => $this->theme_support->get_option( 'thumbnail_image_width', 300 ),
|
||||
'type' => 'option',
|
||||
'capability' => 'manage_woocommerce',
|
||||
'sanitize_callback' => 'absint',
|
||||
|
@ -769,7 +780,7 @@ class WC_Shop_Customizer {
|
|||
);
|
||||
} else {
|
||||
$choose_pages = array(
|
||||
'woocommerce_terms_page_id' => __( 'Terms and conditions', 'woocommerce' ),
|
||||
'woocommerce_terms_page_id' => __( 'Terms and conditions', 'woocommerce' ),
|
||||
);
|
||||
}
|
||||
$pages = get_pages(
|
||||
|
|
|
@ -10,6 +10,8 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
exit;
|
||||
}
|
||||
|
||||
use Automattic\WooCommerce\Internal\AssignDefaultCategory;
|
||||
|
||||
/**
|
||||
* Terms controller class.
|
||||
*/
|
||||
|
@ -563,6 +565,9 @@ abstract class WC_REST_Terms_Controller extends WC_REST_Controller {
|
|||
return new WP_Error( 'woocommerce_rest_cannot_delete', __( 'The resource cannot be deleted.', 'woocommerce' ), array( 'status' => 500 ) );
|
||||
}
|
||||
|
||||
// Schedule action to assign default category.
|
||||
wc_get_container()->get( AssignDefaultCategory::class )->schedule_action();
|
||||
|
||||
/**
|
||||
* Fires after a single term is deleted via the REST API.
|
||||
*
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
* @package WooCommerce\Classes
|
||||
*/
|
||||
|
||||
use Automattic\WooCommerce\Internal\ThemeSupport;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
|
@ -29,8 +31,7 @@ class WC_Twenty_Eleven {
|
|||
add_theme_support( 'wc-product-gallery-zoom' );
|
||||
add_theme_support( 'wc-product-gallery-lightbox' );
|
||||
add_theme_support( 'wc-product-gallery-slider' );
|
||||
add_theme_support(
|
||||
'woocommerce',
|
||||
wc_get_container()->get( ThemeSupport::class )->add_default_options(
|
||||
array(
|
||||
'thumbnail_image_width' => 150,
|
||||
'single_image_width' => 300,
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
* @package WooCommerce\Classes
|
||||
*/
|
||||
|
||||
use Automattic\WooCommerce\Internal\ThemeSupport;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
|
@ -30,8 +32,7 @@ class WC_Twenty_Fifteen {
|
|||
add_theme_support( 'wc-product-gallery-zoom' );
|
||||
add_theme_support( 'wc-product-gallery-lightbox' );
|
||||
add_theme_support( 'wc-product-gallery-slider' );
|
||||
add_theme_support(
|
||||
'woocommerce',
|
||||
wc_get_container()->get( ThemeSupport::class )->add_default_options(
|
||||
array(
|
||||
'thumbnail_image_width' => 200,
|
||||
'single_image_width' => 350,
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
* @package WooCommerce\Classes
|
||||
*/
|
||||
|
||||
use Automattic\WooCommerce\Internal\ThemeSupport;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
|
@ -30,8 +32,7 @@ class WC_Twenty_Fourteen {
|
|||
add_theme_support( 'wc-product-gallery-zoom' );
|
||||
add_theme_support( 'wc-product-gallery-lightbox' );
|
||||
add_theme_support( 'wc-product-gallery-slider' );
|
||||
add_theme_support(
|
||||
'woocommerce',
|
||||
wc_get_container()->get( ThemeSupport::class )->add_default_options(
|
||||
array(
|
||||
'thumbnail_image_width' => 150,
|
||||
'single_image_width' => 300,
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\WooCommerce\Internal\ThemeSupport;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
|
@ -37,8 +38,7 @@ class WC_Twenty_Nineteen {
|
|||
add_theme_support( 'wc-product-gallery-zoom' );
|
||||
add_theme_support( 'wc-product-gallery-lightbox' );
|
||||
add_theme_support( 'wc-product-gallery-slider' );
|
||||
add_theme_support(
|
||||
'woocommerce',
|
||||
wc_get_container()->get( ThemeSupport::class )->add_default_options(
|
||||
array(
|
||||
'thumbnail_image_width' => 300,
|
||||
'single_image_width' => 450,
|
||||
|
@ -48,7 +48,7 @@ class WC_Twenty_Nineteen {
|
|||
// Tweak Twenty Nineteen features.
|
||||
add_action( 'wp', array( __CLASS__, 'tweak_theme_features' ) );
|
||||
|
||||
// Color scheme CSS
|
||||
// Color scheme CSS.
|
||||
add_filter( 'twentynineteen_custom_colors_css', array( __CLASS__, 'custom_colors_css' ), 10, 3 );
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\WooCommerce\Internal\ThemeSupport;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
|
@ -30,8 +31,7 @@ class WC_Twenty_Seventeen {
|
|||
add_theme_support( 'wc-product-gallery-zoom' );
|
||||
add_theme_support( 'wc-product-gallery-lightbox' );
|
||||
add_theme_support( 'wc-product-gallery-slider' );
|
||||
add_theme_support(
|
||||
'woocommerce',
|
||||
wc_get_container()->get( ThemeSupport::class )->add_default_options(
|
||||
array(
|
||||
'thumbnail_image_width' => 250,
|
||||
'single_image_width' => 350,
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
* @package WooCommerce\Classes
|
||||
*/
|
||||
|
||||
use Automattic\WooCommerce\Internal\ThemeSupport;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
|
@ -29,8 +31,7 @@ class WC_Twenty_Sixteen {
|
|||
add_theme_support( 'wc-product-gallery-zoom' );
|
||||
add_theme_support( 'wc-product-gallery-lightbox' );
|
||||
add_theme_support( 'wc-product-gallery-slider' );
|
||||
add_theme_support(
|
||||
'woocommerce',
|
||||
wc_get_container()->get( ThemeSupport::class )->add_default_options(
|
||||
array(
|
||||
'thumbnail_image_width' => 250,
|
||||
'single_image_width' => 400,
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
* @package WooCommerce\Classes
|
||||
*/
|
||||
|
||||
use Automattic\WooCommerce\Internal\ThemeSupport;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
|
@ -29,8 +31,7 @@ class WC_Twenty_Ten {
|
|||
add_theme_support( 'wc-product-gallery-zoom' );
|
||||
add_theme_support( 'wc-product-gallery-lightbox' );
|
||||
add_theme_support( 'wc-product-gallery-slider' );
|
||||
add_theme_support(
|
||||
'woocommerce',
|
||||
wc_get_container()->get( ThemeSupport::class )->add_default_options(
|
||||
array(
|
||||
'thumbnail_image_width' => 200,
|
||||
'single_image_width' => 300,
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
* @package WooCommerce\Classes
|
||||
*/
|
||||
|
||||
use Automattic\WooCommerce\Internal\ThemeSupport;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
|
@ -30,8 +32,7 @@ class WC_Twenty_Thirteen {
|
|||
add_theme_support( 'wc-product-gallery-zoom' );
|
||||
add_theme_support( 'wc-product-gallery-lightbox' );
|
||||
add_theme_support( 'wc-product-gallery-slider' );
|
||||
add_theme_support(
|
||||
'woocommerce',
|
||||
wc_get_container()->get( ThemeSupport::class )->add_default_options(
|
||||
array(
|
||||
'thumbnail_image_width' => 200,
|
||||
'single_image_width' => 300,
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
* @package WooCommerce\Classes
|
||||
*/
|
||||
|
||||
use Automattic\WooCommerce\Internal\ThemeSupport;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
|
@ -30,8 +32,7 @@ class WC_Twenty_Twelve {
|
|||
add_theme_support( 'wc-product-gallery-zoom' );
|
||||
add_theme_support( 'wc-product-gallery-lightbox' );
|
||||
add_theme_support( 'wc-product-gallery-slider' );
|
||||
add_theme_support(
|
||||
'woocommerce',
|
||||
wc_get_container()->get( ThemeSupport::class )->add_default_options(
|
||||
array(
|
||||
'thumbnail_image_width' => 200,
|
||||
'single_image_width' => 300,
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\WooCommerce\Internal\ThemeSupport;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
|
@ -37,8 +38,7 @@ class WC_Twenty_Twenty_One {
|
|||
add_theme_support( 'wc-product-gallery-zoom' );
|
||||
add_theme_support( 'wc-product-gallery-lightbox' );
|
||||
add_theme_support( 'wc-product-gallery-slider' );
|
||||
add_theme_support(
|
||||
'woocommerce',
|
||||
wc_get_container()->get( ThemeSupport::class )->add_default_options(
|
||||
array(
|
||||
'thumbnail_image_width' => 450,
|
||||
'single_image_width' => 600,
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\WooCommerce\Internal\ThemeSupport;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
|
@ -37,8 +38,7 @@ class WC_Twenty_Twenty {
|
|||
add_theme_support( 'wc-product-gallery-zoom' );
|
||||
add_theme_support( 'wc-product-gallery-lightbox' );
|
||||
add_theme_support( 'wc-product-gallery-slider' );
|
||||
add_theme_support(
|
||||
'woocommerce',
|
||||
wc_get_container()->get( ThemeSupport::class )->add_default_options(
|
||||
array(
|
||||
'thumbnail_image_width' => 450,
|
||||
'single_image_width' => 600,
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
*/
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\WooCommerce\Internal\ThemeSupport;
|
||||
use Automattic\WooCommerce\Utilities\NumberUtil;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
|
@ -390,7 +391,7 @@ function wc_locate_template( $template_name, $template_path = '', $default_path
|
|||
// Look within passed path within the theme - this is priority.
|
||||
if ( false !== strpos( $template_name, 'product_cat' ) || false !== strpos( $template_name, 'product_tag' ) ) {
|
||||
$cs_template = str_replace( '_', '-', $template_name );
|
||||
$template = locate_template(
|
||||
$template = locate_template(
|
||||
array(
|
||||
trailingslashit( $template_path ) . $cs_template,
|
||||
$cs_template,
|
||||
|
@ -879,38 +880,7 @@ function wc_mail( $to, $subject, $message, $headers = "Content-Type: text/html\r
|
|||
* @return mixed Value of prop(s).
|
||||
*/
|
||||
function wc_get_theme_support( $prop = '', $default = null ) {
|
||||
$theme_support = get_theme_support( 'woocommerce' );
|
||||
$theme_support = is_array( $theme_support ) ? $theme_support[0] : false;
|
||||
|
||||
if ( ! $theme_support ) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
if ( $prop ) {
|
||||
$prop_stack = explode( '::', $prop );
|
||||
$prop_key = array_shift( $prop_stack );
|
||||
|
||||
if ( isset( $theme_support[ $prop_key ] ) ) {
|
||||
$value = $theme_support[ $prop_key ];
|
||||
|
||||
if ( count( $prop_stack ) ) {
|
||||
foreach ( $prop_stack as $prop_key ) {
|
||||
if ( is_array( $value ) && isset( $value[ $prop_key ] ) ) {
|
||||
$value = $value[ $prop_key ];
|
||||
} else {
|
||||
$value = $default;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$value = $default;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
return $theme_support;
|
||||
return wc_get_container()->get( ThemeSupport::class )->get_option( $prop, $default );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1270,7 +1240,7 @@ function wc_format_country_state_string( $country_string ) {
|
|||
* @return array
|
||||
*/
|
||||
function wc_get_base_location() {
|
||||
$default = apply_filters( 'woocommerce_get_base_location', get_option( 'woocommerce_default_country' ) );
|
||||
$default = apply_filters( 'woocommerce_get_base_location', get_option( 'woocommerce_default_country', 'US:CA' ) );
|
||||
|
||||
return wc_format_country_state_string( $default );
|
||||
}
|
||||
|
@ -1286,7 +1256,7 @@ function wc_get_base_location() {
|
|||
*/
|
||||
function wc_get_customer_default_location() {
|
||||
$set_default_location_to = get_option( 'woocommerce_default_customer_address', 'base' );
|
||||
$default_location = '' === $set_default_location_to ? '' : get_option( 'woocommerce_default_country', '' );
|
||||
$default_location = '' === $set_default_location_to ? '' : get_option( 'woocommerce_default_country', 'US:CA' );
|
||||
$location = wc_format_country_state_string( apply_filters( 'woocommerce_customer_default_location', $default_location ) );
|
||||
|
||||
// Geolocation takes priority if used and if geolocation is possible.
|
||||
|
@ -2269,9 +2239,9 @@ function wc_prevent_dangerous_auto_updates( $should_update, $plugin ) {
|
|||
include_once dirname( __FILE__ ) . '/admin/plugin-updates/class-wc-plugin-updates.php';
|
||||
}
|
||||
|
||||
$new_version = wc_clean( $plugin->new_version );
|
||||
$plugin_updates = new WC_Plugin_Updates();
|
||||
$version_type = Constants::get_constant( 'WC_SSR_PLUGIN_UPDATE_RELEASE_VERSION_TYPE' );
|
||||
$new_version = wc_clean( $plugin->new_version );
|
||||
$plugin_updates = new WC_Plugin_Updates();
|
||||
$version_type = Constants::get_constant( 'WC_SSR_PLUGIN_UPDATE_RELEASE_VERSION_TYPE' );
|
||||
if ( ! is_string( $version_type ) ) {
|
||||
$version_type = 'none';
|
||||
}
|
||||
|
|
|
@ -403,7 +403,7 @@ function wc_get_low_stock_amount( WC_Product $product ) {
|
|||
$low_stock_amount = $product->get_low_stock_amount();
|
||||
|
||||
if ( '' === $low_stock_amount && $product->is_type( 'variation' ) ) {
|
||||
$product = wc_get_product( $product->get_parent_id() );
|
||||
$product = wc_get_product( $product->get_parent_id() );
|
||||
$low_stock_amount = $product->get_low_stock_amount();
|
||||
}
|
||||
|
||||
|
@ -411,5 +411,5 @@ function wc_get_low_stock_amount( WC_Product $product ) {
|
|||
$low_stock_amount = get_option( 'woocommerce_notify_low_stock_amount', 2 );
|
||||
}
|
||||
|
||||
return $low_stock_amount;
|
||||
return (int) $low_stock_amount;
|
||||
}
|
||||
|
|
|
@ -2753,8 +2753,9 @@ if ( ! function_exists( 'woocommerce_form_field' ) ) {
|
|||
$field .= '<input type="hidden" name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" value="' . current( array_keys( $countries ) ) . '" ' . implode( ' ', $custom_attributes ) . ' class="country_to_state" readonly="readonly" />';
|
||||
|
||||
} else {
|
||||
$data_label = ! empty( $args['label'] ) ? 'data-label="' . esc_attr( $args['label'] ) . '"' : '';
|
||||
|
||||
$field = '<select name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" class="country_to_state country_select ' . esc_attr( implode( ' ', $args['input_class'] ) ) . '" ' . implode( ' ', $custom_attributes ) . ' data-placeholder="' . esc_attr( $args['placeholder'] ? $args['placeholder'] : esc_attr__( 'Select a country / region…', 'woocommerce' ) ) . '"><option value="">' . esc_html__( 'Select a country / region…', 'woocommerce' ) . '</option>';
|
||||
$field = '<select name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" class="country_to_state country_select ' . esc_attr( implode( ' ', $args['input_class'] ) ) . '" ' . implode( ' ', $custom_attributes ) . ' data-placeholder="' . esc_attr( $args['placeholder'] ? $args['placeholder'] : esc_attr__( 'Select a country / region…', 'woocommerce' ) ) . '" ' . $data_label . '><option value="">' . esc_html__( 'Select a country / region…', 'woocommerce' ) . '</option>';
|
||||
|
||||
foreach ( $countries as $ckey => $cvalue ) {
|
||||
$field .= '<option value="' . esc_attr( $ckey ) . '" ' . selected( $value, $ckey, false ) . '>' . esc_html( $cvalue ) . '</option>';
|
||||
|
@ -2779,8 +2780,9 @@ if ( ! function_exists( 'woocommerce_form_field' ) ) {
|
|||
$field .= '<input type="hidden" class="hidden" name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" value="" ' . implode( ' ', $custom_attributes ) . ' placeholder="' . esc_attr( $args['placeholder'] ) . '" readonly="readonly" data-input-classes="' . esc_attr( implode( ' ', $args['input_class'] ) ) . '"/>';
|
||||
|
||||
} elseif ( ! is_null( $for_country ) && is_array( $states ) ) {
|
||||
$data_label = ! empty( $args['label'] ) ? 'data-label="' . esc_attr( $args['label'] ) . '"' : '';
|
||||
|
||||
$field .= '<select name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" class="state_select ' . esc_attr( implode( ' ', $args['input_class'] ) ) . '" ' . implode( ' ', $custom_attributes ) . ' data-placeholder="' . esc_attr( $args['placeholder'] ? $args['placeholder'] : esc_html__( 'Select an option…', 'woocommerce' ) ) . '" data-input-classes="' . esc_attr( implode( ' ', $args['input_class'] ) ) . '">
|
||||
$field .= '<select name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" class="state_select ' . esc_attr( implode( ' ', $args['input_class'] ) ) . '" ' . implode( ' ', $custom_attributes ) . ' data-placeholder="' . esc_attr( $args['placeholder'] ? $args['placeholder'] : esc_html__( 'Select an option…', 'woocommerce' ) ) . '" data-input-classes="' . esc_attr( implode( ' ', $args['input_class'] ) ) . '" ' . $data_label . '>
|
||||
<option value="">' . esc_html__( 'Select an option…', 'woocommerce' ) . '</option>';
|
||||
|
||||
foreach ( $states as $ckey => $cvalue ) {
|
||||
|
|
|
@ -486,7 +486,8 @@ function _wc_term_recount( $terms, $taxonomy, $callback = true, $terms_are_term_
|
|||
$term_query['join'] .= " INNER JOIN ( SELECT object_id FROM {$wpdb->term_relationships} INNER JOIN {$wpdb->term_taxonomy} using( term_taxonomy_id ) WHERE term_id IN ( " . implode( ',', array_map( 'absint', $terms_to_count ) ) . ' ) ) AS include_join ON include_join.object_id = p.ID';
|
||||
|
||||
// Get the count.
|
||||
$count = $wpdb->get_var( implode( ' ', $term_query ) ); // WPCS: unprepared SQL ok.
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$count = $wpdb->get_var( implode( ' ', $term_query ) );
|
||||
|
||||
// Update the count.
|
||||
update_term_meta( $term_id, 'product_count_' . $taxonomy->name, absint( $count ) );
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Internal\AssignDefaultCategory;
|
||||
|
||||
/**
|
||||
* Update file paths for 2.0
|
||||
*
|
||||
|
@ -1567,31 +1569,12 @@ function wc_update_330_webhooks() {
|
|||
* Assign default cat to all products with no cats.
|
||||
*/
|
||||
function wc_update_330_set_default_product_cat() {
|
||||
global $wpdb;
|
||||
|
||||
$default_category = get_option( 'default_product_cat', 0 );
|
||||
|
||||
if ( $default_category ) {
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"INSERT INTO {$wpdb->term_relationships} (object_id, term_taxonomy_id)
|
||||
SELECT DISTINCT posts.ID, %s FROM {$wpdb->posts} posts
|
||||
LEFT JOIN
|
||||
(
|
||||
SELECT object_id FROM {$wpdb->term_relationships} term_relationships
|
||||
LEFT JOIN {$wpdb->term_taxonomy} term_taxonomy ON term_relationships.term_taxonomy_id = term_taxonomy.term_taxonomy_id
|
||||
WHERE term_taxonomy.taxonomy = 'product_cat'
|
||||
) AS tax_query
|
||||
ON posts.ID = tax_query.object_id
|
||||
WHERE posts.post_type = 'product'
|
||||
AND tax_query.object_id IS NULL",
|
||||
$default_category
|
||||
)
|
||||
);
|
||||
wp_cache_flush();
|
||||
delete_transient( 'wc_term_counts' );
|
||||
wp_update_term_count_now( array( $default_category ), 'product_cat' );
|
||||
}
|
||||
/*
|
||||
* When a product category is deleted, we need to check
|
||||
* if the product has no categories assigned. Then assign
|
||||
* it a default category.
|
||||
*/
|
||||
wc_get_container()->get( AssignDefaultCategory::class )->maybe_assign_default_product_cat();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "woocommerce",
|
||||
"version": "5.1.0",
|
||||
"version": "5.3.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
|
18
readme.txt
18
readme.txt
|
@ -4,7 +4,7 @@ Tags: e-commerce, store, sales, sell, woo, shop, cart, checkout, downloadable, d
|
|||
Requires at least: 5.5
|
||||
Tested up to: 5.7
|
||||
Requires PHP: 7.0
|
||||
Stable tag: 5.2.0
|
||||
Stable tag: 5.2.2
|
||||
License: GPLv3
|
||||
License URI: https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
|
@ -160,6 +160,22 @@ WooCommerce comes with some sample data you can use to see how products look; im
|
|||
|
||||
== Changelog ==
|
||||
|
||||
= 5.2.2 2021-04-15 =
|
||||
|
||||
**WooCommerce**
|
||||
|
||||
* Fix - Can't grant permission for download from order details page. #29691
|
||||
|
||||
= 5.2.1 2021-04-14 =
|
||||
|
||||
**WooCommerce**
|
||||
|
||||
* Update - WooCommerce Blocks package 4.7.2. #29660
|
||||
|
||||
**WooCommerce Blocks - 4.7.2**
|
||||
|
||||
* Fix - Check if Cart and Checkout are registered before removing payment methods. ([4056](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/4056))
|
||||
|
||||
= 5.2.0 2021-04-13 =
|
||||
|
||||
**WooCommerce**
|
||||
|
|
|
@ -7,12 +7,14 @@ namespace Automattic\WooCommerce;
|
|||
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\ExtendedContainer;
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\DownloadPermissionsAdjusterServiceProvider;
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\AssignDefaultCategoryServiceProvider;
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\ProxiesServiceProvider;
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\ThemeManagementServiceProvider;
|
||||
|
||||
/**
|
||||
* PSR11 compliant dependency injection container for WooCommerce.
|
||||
*
|
||||
* Classes in the `src` directory should specify dependencies from that directory via constructor arguments
|
||||
* Classes in the `src` directory should specify dependencies from that directory via an 'init' method having arguments
|
||||
* with type hints. If an instance of the container itself is needed, the type hint to use is \Psr\Container\ContainerInterface.
|
||||
*
|
||||
* Classes in the `src` directory should interact with anything outside (especially code in the `includes` directory
|
||||
|
@ -34,7 +36,9 @@ final class Container implements \Psr\Container\ContainerInterface {
|
|||
*/
|
||||
private $service_providers = array(
|
||||
ProxiesServiceProvider::class,
|
||||
ThemeManagementServiceProvider::class,
|
||||
DownloadPermissionsAdjusterServiceProvider::class,
|
||||
AssignDefaultCategoryServiceProvider::class,
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
/**
|
||||
* AssignDefaultCategory class file.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Internal;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Class to assign default category to products.
|
||||
*/
|
||||
class AssignDefaultCategory {
|
||||
/**
|
||||
* Class initialization, to be executed when the class is resolved by the container.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final public function init() {
|
||||
add_action( 'wc_schedule_update_product_default_cat', array( $this, 'maybe_assign_default_product_cat' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* When a product category is deleted, we need to check
|
||||
* if the product has no categories assigned. Then assign
|
||||
* it a default category. We delay this with a scheduled
|
||||
* action job to not block the response.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function schedule_action() {
|
||||
WC()->queue()->schedule_single(
|
||||
time(),
|
||||
'wc_schedule_update_product_default_cat',
|
||||
array(),
|
||||
'wc_update_product_default_cat'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns default product category for products
|
||||
* that have no categories.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function maybe_assign_default_product_cat() {
|
||||
global $wpdb;
|
||||
|
||||
$default_category = get_option( 'default_product_cat', 0 );
|
||||
|
||||
if ( $default_category ) {
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"INSERT INTO {$wpdb->term_relationships} (object_id, term_taxonomy_id)
|
||||
SELECT DISTINCT posts.ID, %s FROM {$wpdb->posts} posts
|
||||
LEFT JOIN
|
||||
(
|
||||
SELECT object_id FROM {$wpdb->term_relationships} term_relationships
|
||||
LEFT JOIN {$wpdb->term_taxonomy} term_taxonomy ON term_relationships.term_taxonomy_id = term_taxonomy.term_taxonomy_id
|
||||
WHERE term_taxonomy.taxonomy = 'product_cat'
|
||||
) AS tax_query
|
||||
ON posts.ID = tax_query.object_id
|
||||
WHERE posts.post_type = 'product'
|
||||
AND tax_query.object_id IS NULL",
|
||||
$default_category
|
||||
)
|
||||
);
|
||||
wp_cache_flush();
|
||||
delete_transient( 'wc_term_counts' );
|
||||
wp_update_term_count_now( array( $default_category ), 'product_cat' );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
/**
|
||||
* AssignDefaultCategoryServiceProvider class file.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders;
|
||||
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\AbstractServiceProvider;
|
||||
use Automattic\WooCommerce\Internal\AssignDefaultCategory;
|
||||
|
||||
/**
|
||||
* Service provider for the AssignDefaultCategory class.
|
||||
*/
|
||||
class AssignDefaultCategoryServiceProvider extends AbstractServiceProvider {
|
||||
|
||||
/**
|
||||
* The classes/interfaces that are serviced by this service provider.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $provides = array(
|
||||
AssignDefaultCategory::class,
|
||||
);
|
||||
|
||||
/**
|
||||
* Register the classes.
|
||||
*/
|
||||
public function register() {
|
||||
$this->share( AssignDefaultCategory::class );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
/**
|
||||
* ThemeManagementServiceProvider class file.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders;
|
||||
|
||||
use Automattic\WooCommerce\Internal\ThemeSupport;
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\AbstractServiceProvider;
|
||||
|
||||
/**
|
||||
* Service provider for the classes in the Automattic\WooCommerce\ThemeManagement namespace.
|
||||
*/
|
||||
class ThemeManagementServiceProvider extends AbstractServiceProvider {
|
||||
|
||||
/**
|
||||
* The classes/interfaces that are serviced by this service provider.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $provides = array(
|
||||
ThemeSupport::class,
|
||||
);
|
||||
|
||||
/**
|
||||
* Register the classes.
|
||||
*/
|
||||
public function register() {
|
||||
$this->share_with_auto_arguments( ThemeSupport::class );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
/**
|
||||
* ThemeSupport class file.
|
||||
*
|
||||
* @package Automattic\WooCommerce\ThemeManagement
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Internal;
|
||||
|
||||
use Automattic\WooCommerce\Proxies\LegacyProxy;
|
||||
use Automattic\WooCommerce\Utilities\ArrayUtil;
|
||||
|
||||
/**
|
||||
* Provides methods for theme support.
|
||||
*/
|
||||
class ThemeSupport {
|
||||
|
||||
const DEFAULTS_KEY = '_defaults';
|
||||
|
||||
/**
|
||||
* The instance of LegacyProxy to use.
|
||||
*
|
||||
* @var LegacyProxy
|
||||
*/
|
||||
private $legacy_proxy;
|
||||
|
||||
/**
|
||||
* ThemeSupport constructor.
|
||||
*
|
||||
* @internal
|
||||
* @param LegacyProxy $legacy_proxy The instance of LegacyProxy to use.
|
||||
*/
|
||||
final public function init( LegacyProxy $legacy_proxy ) {
|
||||
$this->legacy_proxy = $legacy_proxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds theme support options for the current theme.
|
||||
*
|
||||
* @param array $options The options to be added.
|
||||
*/
|
||||
public function add_options( $options ) {
|
||||
$this->legacy_proxy->call_function( 'add_theme_support', 'woocommerce', $options );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds default theme support options for the current theme.
|
||||
*
|
||||
* @param array $options The options to be added.
|
||||
*/
|
||||
public function add_default_options( $options ) {
|
||||
$default_options = $this->get_option( self::DEFAULTS_KEY, array() );
|
||||
$default_options = array_merge( $default_options, $options );
|
||||
$this->add_options( array( self::DEFAULTS_KEY => $default_options ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets "theme support" options from the current theme, if set.
|
||||
*
|
||||
* @param string $option_name Option name, possibly nested (key::subkey), to get specific value. Blank to get all the existing options as an array.
|
||||
* @param mixed $default_value Value to return if the specified option doesn't exist.
|
||||
* @return mixed The retrieved option or the default value.
|
||||
*/
|
||||
public function get_option( $option_name = '', $default_value = null ) {
|
||||
$theme_support_options = $this->get_all_options();
|
||||
|
||||
if ( ! $theme_support_options ) {
|
||||
return $default_value;
|
||||
}
|
||||
|
||||
if ( $option_name ) {
|
||||
$value = ArrayUtil::get_nested_value( $theme_support_options, $option_name );
|
||||
if ( is_null( $value ) ) {
|
||||
$value = ArrayUtil::get_nested_value( $theme_support_options, self::DEFAULTS_KEY . '::' . $option_name, $default_value );
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
return $theme_support_options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given theme support option has been defined.
|
||||
*
|
||||
* @param string $option_name The (possibly nested) name of the option to check.
|
||||
* @param bool $include_defaults True to include the default values in the check, false otherwise.
|
||||
*
|
||||
* @return bool True if the specified theme support option has been defined, false otherwise.
|
||||
*/
|
||||
public function has_option( $option_name, $include_defaults = true ) {
|
||||
$theme_support_options = $this->get_all_options();
|
||||
|
||||
if ( ! $theme_support_options ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$value = ArrayUtil::get_nested_value( $theme_support_options, $option_name );
|
||||
if ( ! is_null( $value ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( ! $include_defaults ) {
|
||||
return false;
|
||||
}
|
||||
$value = ArrayUtil::get_nested_value( $theme_support_options, self::DEFAULTS_KEY . '::' . $option_name );
|
||||
return ! is_null( $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the defined theme support options for the 'woocommerce' feature.
|
||||
*
|
||||
* @return array An array with all the theme support options defined for the 'woocommerce' feature, or false if nothing has been defined for that feature.
|
||||
*/
|
||||
private function get_all_options() {
|
||||
$theme_support = $this->legacy_proxy->call_function( 'get_theme_support', 'woocommerce' );
|
||||
return is_array( $theme_support ) ? $theme_support[0] : false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
/**
|
||||
* A class of utilities for dealing with arrays.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Utilities;
|
||||
|
||||
/**
|
||||
* A class of utilities for dealing with arrays.
|
||||
*/
|
||||
class ArrayUtil {
|
||||
/**
|
||||
* Get a value from an nested array by specifying the entire key hierarchy with '::' as separator.
|
||||
*
|
||||
* E.g. for [ 'foo' => [ 'bar' => [ 'fizz' => 'buzz' ] ] ] the value for key 'foo::bar::fizz' would be 'buzz'.
|
||||
*
|
||||
* @param array $array The array to get the value from.
|
||||
* @param string $key The complete key hierarchy, using '::' as separator.
|
||||
* @param mixed $default The value to return if the key doesn't exist in the array.
|
||||
*
|
||||
* @return mixed The retrieved value, or the supplied default value.
|
||||
* @throws \Exception $array is not an array.
|
||||
*/
|
||||
public static function get_nested_value( array $array, string $key, $default = null ) {
|
||||
$key_stack = explode( '::', $key );
|
||||
$subkey = array_shift( $key_stack );
|
||||
|
||||
if ( isset( $array[ $subkey ] ) ) {
|
||||
$value = $array[ $subkey ];
|
||||
|
||||
if ( count( $key_stack ) ) {
|
||||
foreach ( $key_stack as $subkey ) {
|
||||
if ( is_array( $value ) && isset( $value[ $subkey ] ) ) {
|
||||
$value = $value[ $subkey ];
|
||||
} else {
|
||||
$value = $default;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$value = $default;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@woocommerce/api",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.2",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@ -4895,9 +4895,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"y18n": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
|
||||
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz",
|
||||
"integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==",
|
||||
"dev": true
|
||||
},
|
||||
"yargs": {
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
switchUserToTest,
|
||||
clearLocalStorage,
|
||||
setBrowserViewport,
|
||||
factories,
|
||||
withRestApi,
|
||||
} from '@woocommerce/e2e-utils';
|
||||
|
||||
const { merchant } = require( '@woocommerce/e2e-utils' );
|
||||
|
@ -38,50 +38,13 @@ async function trashExistingPosts() {
|
|||
await switchUserToTest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use api package to delete products.
|
||||
*
|
||||
* @return {Promise} Promise resolving once products have been trashed.
|
||||
*/
|
||||
async function deleteAllProducts() {
|
||||
const repository = SimpleProduct.restRepository( factories.api.withDefaultPermalinks );
|
||||
let products;
|
||||
|
||||
products = await repository.list();
|
||||
while ( products.length > 0 ) {
|
||||
for( let p = 0; p < products.length; p++ ) {
|
||||
await repository.delete( products[ p ].id );
|
||||
}
|
||||
products = await repository.list();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use api package to delete coupons.
|
||||
*
|
||||
* @return {Promise} Promise resolving once coupons have been trashed.
|
||||
*/
|
||||
async function deleteAllCoupons() {
|
||||
const repository = Coupon.restRepository( factories.api.withDefaultPermalinks );
|
||||
let coupons;
|
||||
|
||||
coupons = await repository.list();
|
||||
|
||||
while ( coupons.length > 0 ) {
|
||||
for (let c = 0; c < coupons.length; c++ ) {
|
||||
await repository.delete( coupons[ c ].id );
|
||||
}
|
||||
coupons = await repository.list();
|
||||
}
|
||||
}
|
||||
|
||||
// Before every test suite run, delete all content created by the test. This ensures
|
||||
// other posts/comments/etc. aren't dirtying tests and tests don't depend on
|
||||
// each other's side-effects.
|
||||
beforeAll( async () => {
|
||||
await trashExistingPosts();
|
||||
await deleteAllProducts();
|
||||
await deleteAllCoupons();
|
||||
await withRestApi.deleteAllProducts();
|
||||
await withRestApi.deleteAllCoupons();
|
||||
await clearLocalStorage();
|
||||
await setBrowserViewport( 'large' );
|
||||
} );
|
||||
|
|
|
@ -1,11 +1,22 @@
|
|||
# Unreleased
|
||||
|
||||
## Added
|
||||
|
||||
- Support for re-running setup and shopper tests
|
||||
- Shopper Order Email Receiving
|
||||
|
||||
## Fixed
|
||||
|
||||
- Checkout create account test would fail if configuration value `addresses.customer.billing.email` was not `john.doe@example.com`
|
||||
|
||||
# 0.1.3
|
||||
|
||||
## Added
|
||||
|
||||
- Shopper Checkout Login Account
|
||||
- Shopper My Account Create Account
|
||||
- Shopper Cart Calculate Shipping
|
||||
- Shopper Cart Redirection
|
||||
|
||||
## Fixed
|
||||
|
||||
|
|
|
@ -30,6 +30,11 @@ runShopperTests();
|
|||
|
||||
```
|
||||
|
||||
## Retrying/Re-running tests
|
||||
|
||||
On a new site, the setup and activation tests prepare the site for the remainder of the tests. To retry/rerun the test suite on a site where setup/onboarding test have already run use the environment variable `E2E_RETEST=1`.
|
||||
|
||||
|
||||
## Test functions
|
||||
|
||||
The functions to access the core tests are:
|
||||
|
@ -80,6 +85,9 @@ The functions to access the core tests are:
|
|||
- `runCheckoutCreateAccountTest` - Shopper can create an account during checkout
|
||||
- `runCheckoutLoginAccountTest` - Shopper can login to an account during checkout
|
||||
- `runMyAccountCreateAccountTest` - Shopper can create an account via my account page
|
||||
- `runCartCalculateShippingTest` - Shopper can calculate shipping in the cart
|
||||
- `runCartRedirectionTest` - Shopper is redirected to the cart page after adding to cart
|
||||
- `runOrderEmailReceivingTest` - Shopper can receive an email for his order
|
||||
|
||||
### REST API
|
||||
|
||||
|
|
|
@ -0,0 +1,454 @@
|
|||
{
|
||||
"name": "@woocommerce/e2e-core-tests",
|
||||
"version": "0.1.3",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": {
|
||||
"version": "7.12.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz",
|
||||
"integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==",
|
||||
"requires": {
|
||||
"@babel/highlight": "^7.12.13"
|
||||
}
|
||||
},
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.12.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
|
||||
"integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw=="
|
||||
},
|
||||
"@babel/highlight": {
|
||||
"version": "7.13.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz",
|
||||
"integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==",
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.12.11",
|
||||
"chalk": "^2.0.0",
|
||||
"js-tokens": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"requires": {
|
||||
"color-convert": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"requires": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"requires": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@jest/environment": {
|
||||
"version": "26.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.5.2.tgz",
|
||||
"integrity": "sha512-YjhCD/Zhkz0/1vdlS/QN6QmuUdDkpgBdK4SdiVg4Y19e29g4VQYN5Xg8+YuHjdoWGY7wJHMxc79uDTeTOy9Ngw==",
|
||||
"requires": {
|
||||
"@jest/fake-timers": "^26.5.2",
|
||||
"@jest/types": "^26.5.2",
|
||||
"@types/node": "*",
|
||||
"jest-mock": "^26.5.2"
|
||||
}
|
||||
},
|
||||
"@jest/fake-timers": {
|
||||
"version": "26.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.5.2.tgz",
|
||||
"integrity": "sha512-09Hn5Oraqt36V1akxQeWMVL0fR9c6PnEhpgLaYvREXZJAh2H2Y+QLCsl0g7uMoJeoWJAuz4tozk1prbR1Fc1sw==",
|
||||
"requires": {
|
||||
"@jest/types": "^26.5.2",
|
||||
"@sinonjs/fake-timers": "^6.0.1",
|
||||
"@types/node": "*",
|
||||
"jest-message-util": "^26.5.2",
|
||||
"jest-mock": "^26.5.2",
|
||||
"jest-util": "^26.5.2"
|
||||
}
|
||||
},
|
||||
"@jest/globals": {
|
||||
"version": "26.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.5.3.tgz",
|
||||
"integrity": "sha512-7QztI0JC2CuB+Wx1VdnOUNeIGm8+PIaqngYsZXQCkH2QV0GFqzAYc9BZfU0nuqA6cbYrWh5wkuMzyii3P7deug==",
|
||||
"requires": {
|
||||
"@jest/environment": "^26.5.2",
|
||||
"@jest/types": "^26.5.2",
|
||||
"expect": "^26.5.3"
|
||||
}
|
||||
},
|
||||
"@jest/types": {
|
||||
"version": "26.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@jest/types/-/types-26.5.2.tgz",
|
||||
"integrity": "sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==",
|
||||
"requires": {
|
||||
"@types/istanbul-lib-coverage": "^2.0.0",
|
||||
"@types/istanbul-reports": "^3.0.0",
|
||||
"@types/node": "*",
|
||||
"@types/yargs": "^15.0.0",
|
||||
"chalk": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"@sinonjs/commons": {
|
||||
"version": "1.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz",
|
||||
"integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==",
|
||||
"requires": {
|
||||
"type-detect": "4.0.8"
|
||||
}
|
||||
},
|
||||
"@sinonjs/fake-timers": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz",
|
||||
"integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==",
|
||||
"requires": {
|
||||
"@sinonjs/commons": "^1.7.0"
|
||||
}
|
||||
},
|
||||
"@types/istanbul-lib-coverage": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz",
|
||||
"integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw=="
|
||||
},
|
||||
"@types/istanbul-lib-report": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz",
|
||||
"integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==",
|
||||
"requires": {
|
||||
"@types/istanbul-lib-coverage": "*"
|
||||
}
|
||||
},
|
||||
"@types/istanbul-reports": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz",
|
||||
"integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==",
|
||||
"requires": {
|
||||
"@types/istanbul-lib-report": "*"
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "14.14.41",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.41.tgz",
|
||||
"integrity": "sha512-dueRKfaJL4RTtSa7bWeTK1M+VH+Gns73oCgzvYfHZywRCoPSd8EkXBL0mZ9unPTveBn+D9phZBaxuzpwjWkW0g=="
|
||||
},
|
||||
"@types/stack-utils": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz",
|
||||
"integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw=="
|
||||
},
|
||||
"@types/yargs": {
|
||||
"version": "15.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz",
|
||||
"integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==",
|
||||
"requires": {
|
||||
"@types/yargs-parser": "*"
|
||||
}
|
||||
},
|
||||
"@types/yargs-parser": {
|
||||
"version": "20.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.0.tgz",
|
||||
"integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA=="
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
|
||||
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"requires": {
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"requires": {
|
||||
"fill-range": "^7.0.1"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
||||
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"ci-info": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
|
||||
"integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ=="
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"requires": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"config": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/config/-/config-3.3.3.tgz",
|
||||
"integrity": "sha512-T3RmZQEAji5KYqUQpziWtyGJFli6Khz7h0rpxDwYNjSkr5ynyTWwO7WpfjHzTXclNCDfSWQRcwMb+NwxJesCKw==",
|
||||
"requires": {
|
||||
"json5": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"diff-sequences": {
|
||||
"version": "26.5.0",
|
||||
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.5.0.tgz",
|
||||
"integrity": "sha512-ZXx86srb/iYy6jG71k++wBN9P9J05UNQ5hQHQd9MtMPvcqXPx/vKU69jfHV637D00Q2gSgPk2D+jSx3l1lDW/Q=="
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
|
||||
"integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="
|
||||
},
|
||||
"expect": {
|
||||
"version": "26.5.3",
|
||||
"resolved": "https://registry.npmjs.org/expect/-/expect-26.5.3.tgz",
|
||||
"integrity": "sha512-kkpOhGRWGOr+TEFUnYAjfGvv35bfP+OlPtqPIJpOCR9DVtv8QV+p8zG0Edqafh80fsjeE+7RBcVUq1xApnYglw==",
|
||||
"requires": {
|
||||
"@jest/types": "^26.5.2",
|
||||
"ansi-styles": "^4.0.0",
|
||||
"jest-get-type": "^26.3.0",
|
||||
"jest-matcher-utils": "^26.5.2",
|
||||
"jest-message-util": "^26.5.2",
|
||||
"jest-regex-util": "^26.0.0"
|
||||
}
|
||||
},
|
||||
"faker": {
|
||||
"version": "5.5.3",
|
||||
"resolved": "https://registry.npmjs.org/faker/-/faker-5.5.3.tgz",
|
||||
"integrity": "sha512-wLTv2a28wjUyWkbnX7u/ABZBkUkIF2fCd73V6P2oFqEGEktDfzWx4UxrSqtPRw0xPRAcjeAOIiJWqZm3pP4u3g=="
|
||||
},
|
||||
"fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"requires": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "4.2.6",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
|
||||
"integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ=="
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
|
||||
},
|
||||
"is-ci": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz",
|
||||
"integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==",
|
||||
"requires": {
|
||||
"ci-info": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
|
||||
},
|
||||
"jest-diff": {
|
||||
"version": "26.5.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.5.2.tgz",
|
||||
"integrity": "sha512-HCSWDUGwsov5oTlGzrRM+UPJI/Dpqi9jzeV0fdRNi3Ch5bnoXhnyJMmVg2juv9081zLIy3HGPI5mcuGgXM2xRA==",
|
||||
"requires": {
|
||||
"chalk": "^4.0.0",
|
||||
"diff-sequences": "^26.5.0",
|
||||
"jest-get-type": "^26.3.0",
|
||||
"pretty-format": "^26.5.2"
|
||||
}
|
||||
},
|
||||
"jest-get-type": {
|
||||
"version": "26.3.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz",
|
||||
"integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig=="
|
||||
},
|
||||
"jest-matcher-utils": {
|
||||
"version": "26.5.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.5.2.tgz",
|
||||
"integrity": "sha512-W9GO9KBIC4gIArsNqDUKsLnhivaqf8MSs6ujO/JDcPIQrmY+aasewweXVET8KdrJ6ADQaUne5UzysvF/RR7JYA==",
|
||||
"requires": {
|
||||
"chalk": "^4.0.0",
|
||||
"jest-diff": "^26.5.2",
|
||||
"jest-get-type": "^26.3.0",
|
||||
"pretty-format": "^26.5.2"
|
||||
}
|
||||
},
|
||||
"jest-message-util": {
|
||||
"version": "26.5.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.5.2.tgz",
|
||||
"integrity": "sha512-Ocp9UYZ5Jl15C5PNsoDiGEk14A4NG0zZKknpWdZGoMzJuGAkVt10e97tnEVMYpk7LnQHZOfuK2j/izLBMcuCZw==",
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.0.0",
|
||||
"@jest/types": "^26.5.2",
|
||||
"@types/stack-utils": "^2.0.0",
|
||||
"chalk": "^4.0.0",
|
||||
"graceful-fs": "^4.2.4",
|
||||
"micromatch": "^4.0.2",
|
||||
"slash": "^3.0.0",
|
||||
"stack-utils": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"jest-mock": {
|
||||
"version": "26.5.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.5.2.tgz",
|
||||
"integrity": "sha512-9SiU4b5PtO51v0MtJwVRqeGEroH66Bnwtq4ARdNP7jNXbpT7+ByeWNAk4NeT/uHfNSVDXEXgQo1XRuwEqS6Rdw==",
|
||||
"requires": {
|
||||
"@jest/types": "^26.5.2",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"jest-regex-util": {
|
||||
"version": "26.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz",
|
||||
"integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A=="
|
||||
},
|
||||
"jest-util": {
|
||||
"version": "26.5.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.5.2.tgz",
|
||||
"integrity": "sha512-WTL675bK+GSSAYgS8z9FWdCT2nccO1yTIplNLPlP0OD8tUk/H5IrWKMMRudIQQ0qp8bb4k+1Qa8CxGKq9qnYdg==",
|
||||
"requires": {
|
||||
"@jest/types": "^26.5.2",
|
||||
"@types/node": "*",
|
||||
"chalk": "^4.0.0",
|
||||
"graceful-fs": "^4.2.4",
|
||||
"is-ci": "^2.0.0",
|
||||
"micromatch": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||
},
|
||||
"json5": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
|
||||
"integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==",
|
||||
"requires": {
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"micromatch": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
|
||||
"integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==",
|
||||
"requires": {
|
||||
"braces": "^3.0.1",
|
||||
"picomatch": "^2.0.5"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||
},
|
||||
"picomatch": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz",
|
||||
"integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg=="
|
||||
},
|
||||
"pretty-format": {
|
||||
"version": "26.5.2",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.5.2.tgz",
|
||||
"integrity": "sha512-VizyV669eqESlkOikKJI8Ryxl/kPpbdLwNdPs2GrbQs18MpySB5S0Yo0N7zkg2xTRiFq4CFw8ct5Vg4a0xP0og==",
|
||||
"requires": {
|
||||
"@jest/types": "^26.5.2",
|
||||
"ansi-regex": "^5.0.0",
|
||||
"ansi-styles": "^4.0.0",
|
||||
"react-is": "^16.12.0"
|
||||
}
|
||||
},
|
||||
"react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"slash": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
|
||||
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="
|
||||
},
|
||||
"stack-utils": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.2.tgz",
|
||||
"integrity": "sha512-0H7QK2ECz3fyZMzQ8rH0j2ykpfbnd20BFtfg/SqVC2+sCTtcw0aDTGB7dk+de4U4uUeuz6nOtJcrkFFLG1B0Rg==",
|
||||
"requires": {
|
||||
"escape-string-regexp": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"requires": {
|
||||
"has-flag": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"requires": {
|
||||
"is-number": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"type-detect": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
|
||||
"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g=="
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,7 +11,8 @@
|
|||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"@jest/globals": "^26.4.2",
|
||||
"config": "3.3.3"
|
||||
"config": "3.3.3",
|
||||
"faker": "^5.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@woocommerce/api": "^0.1.2",
|
||||
|
|
|
@ -1,25 +1,46 @@
|
|||
/* eslint-disable jest/no-export, jest/no-disabled-tests */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
const {
|
||||
merchant,
|
||||
completeOnboardingWizard,
|
||||
withRestApi,
|
||||
addShippingZoneAndMethod,
|
||||
IS_RETEST_MODE,
|
||||
} = require( '@woocommerce/e2e-utils' );
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
const config = require( 'config' );
|
||||
const {
|
||||
it,
|
||||
describe,
|
||||
} = require( '@jest/globals' );
|
||||
|
||||
const shippingZoneNameUS = config.get( 'addresses.customer.shipping.country' );
|
||||
|
||||
const runOnboardingFlowTest = () => {
|
||||
describe('Store owner can go through store Onboarding', () => {
|
||||
if ( IS_RETEST_MODE ) {
|
||||
it('can reset onboarding to default settings', async () => {
|
||||
await withRestApi.resetOnboarding();
|
||||
});
|
||||
|
||||
it('can reset shipping zones to default settings', async () => {
|
||||
await withRestApi.deleteAllShippingZones();
|
||||
});
|
||||
|
||||
it('can reset to default settings', async () => {
|
||||
await withRestApi.resetSettingsGroupToDefault('general');
|
||||
await withRestApi.resetSettingsGroupToDefault('products');
|
||||
await withRestApi.resetSettingsGroupToDefault('tax');
|
||||
});
|
||||
}
|
||||
|
||||
it('can start and complete onboarding when visiting the site for the first time.', async () => {
|
||||
await merchant.runSetupWizard();
|
||||
await completeOnboardingWizard();
|
||||
});
|
||||
});
|
||||
|
@ -33,24 +54,25 @@ const runTaskListTest = () => {
|
|||
});
|
||||
// Query for all tasks on the list
|
||||
const taskListItems = await page.$$('.woocommerce-list__item-title');
|
||||
expect(taskListItems).toHaveLength(6);
|
||||
expect(taskListItems.length).toBeInRange( 5, 6 );
|
||||
|
||||
const [ setupTaskListItem ] = await page.$x( '//div[contains(text(),"Set up shipping")]' );
|
||||
await Promise.all([
|
||||
// Work around for https://github.com/woocommerce/woocommerce-admin/issues/6761
|
||||
if ( taskListItems.length == 6 ) {
|
||||
// Click on "Set up shipping" task to move to the next step
|
||||
setupTaskListItem.click(),
|
||||
const [ setupTaskListItem ] = await page.$x( '//div[contains(text(),"Set up shipping")]' );
|
||||
await setupTaskListItem.click();
|
||||
|
||||
// Wait for shipping setup section to load
|
||||
page.waitForNavigation({waitUntil: 'networkidle0'}),
|
||||
]);
|
||||
// Wait for "Proceed" button to become active
|
||||
await page.waitForSelector('button.is-primary:not(:disabled)');
|
||||
await page.waitFor(3000);
|
||||
|
||||
// Wait for "Proceed" button to become active
|
||||
await page.waitForSelector('button.is-primary:not(:disabled)');
|
||||
await page.waitFor(3000);
|
||||
|
||||
// Click on "Proceed" button to save shipping settings
|
||||
await page.click('button.is-primary');
|
||||
await page.waitFor(3000);
|
||||
// Click on "Proceed" button to save shipping settings
|
||||
await page.click('button.is-primary');
|
||||
await page.waitFor(3000);
|
||||
} else {
|
||||
await merchant.openNewShipping();
|
||||
await addShippingZoneAndMethod(shippingZoneNameUS);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -6,7 +6,6 @@ const { HTTPClientFactory } = require( '@woocommerce/api' );
|
|||
const {
|
||||
it,
|
||||
describe,
|
||||
beforeAll,
|
||||
} = require( '@jest/globals' );
|
||||
|
||||
/**
|
||||
|
|
|
@ -21,9 +21,13 @@ const runSingleProductPageTest = require( './shopper/front-end-single-product.te
|
|||
const runVariableProductUpdateTest = require( './shopper/front-end-variable-product-updates.test' );
|
||||
const runCheckoutCreateAccountTest = require( './shopper/front-end-checkout-create-account.test' );
|
||||
const runCheckoutLoginAccountTest = require( './shopper/front-end-checkout-login-account.test' );
|
||||
const runCartCalculateShippingTest = require( './shopper/front-end-cart-calculate-shipping.test' );
|
||||
const runCartRedirectionTest = require( './shopper/front-end-cart-redirection.test' );
|
||||
const runOrderEmailReceivingTest = require( './shopper/front-end-order-email-receiving.test' );
|
||||
|
||||
// Merchant tests
|
||||
const runAddNewShippingZoneTest = require ( './merchant/wp-admin-settings-shipping-zones.test' );
|
||||
const runAddShippingClassesTest = require('./merchant/wp-admin-settings-shipping-classes.test')
|
||||
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' );
|
||||
|
@ -41,6 +45,7 @@ const runMerchantOrderEmailsTest = require( './merchant/wp-admin-order-emails.te
|
|||
const runOrderSearchingTest = require( './merchant/wp-admin-order-searching.test' );
|
||||
const runAnalyticsPageLoadsTest = require( './merchant/wp-admin-analytics-page-loads.test' );
|
||||
const runImportProductsTest = require( './merchant/wp-admin-product-import-csv.test' );
|
||||
const runInitiateWccomConnectionTest = require( './merchant/wp-admin-extensions-connect-wccom.test' );
|
||||
|
||||
// REST API tests
|
||||
const runExternalProductAPITest = require( './api/external-product.test' );
|
||||
|
@ -68,9 +73,13 @@ const runShopperTests = () => {
|
|||
runVariableProductUpdateTest();
|
||||
runCheckoutCreateAccountTest();
|
||||
runCheckoutLoginAccountTest();
|
||||
runCartCalculateShippingTest();
|
||||
runCartRedirectionTest();
|
||||
runOrderEmailReceivingTest();
|
||||
};
|
||||
|
||||
const runMerchantTests = () => {
|
||||
runAddShippingClassesTest();
|
||||
runImportProductsTest();
|
||||
runOrderSearchingTest();
|
||||
runAddNewShippingZoneTest();
|
||||
|
@ -89,6 +98,7 @@ const runMerchantTests = () => {
|
|||
runProductSearchTest();
|
||||
runMerchantOrdersCustomerPaymentPage();
|
||||
runAnalyticsPageLoadsTest();
|
||||
runInitiateWccomConnectionTest();
|
||||
}
|
||||
|
||||
const runApiTests = () => {
|
||||
|
@ -137,9 +147,14 @@ module.exports = {
|
|||
runAddNewShippingZoneTest,
|
||||
runProductBrowseSearchSortTest,
|
||||
runApiTests,
|
||||
runAddShippingClassesTest,
|
||||
runAnalyticsPageLoadsTest,
|
||||
runCheckoutCreateAccountTest,
|
||||
runImportProductsTest,
|
||||
runImportProductsTest,
|
||||
runCheckoutLoginAccountTest,
|
||||
runCartCalculateShippingTest,
|
||||
runCartRedirectionTest,
|
||||
runMyAccountCreateAccountTest,
|
||||
runOrderEmailReceivingTest,
|
||||
runInitiateWccomConnectionTest,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
/* eslint-disable jest/no-export, jest/no-disabled-tests */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
const {
|
||||
merchant,
|
||||
completeOnboardingWizard,
|
||||
withRestApi,
|
||||
addShippingZoneAndMethod,
|
||||
IS_RETEST_MODE,
|
||||
} = require( '@woocommerce/e2e-utils' );
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
const config = require( 'config' );
|
||||
const {
|
||||
it,
|
||||
describe,
|
||||
} = require( '@jest/globals' );
|
||||
|
||||
const shippingZoneNameUS = config.get( 'addresses.customer.shipping.country' );
|
||||
|
||||
const runOnboardingFlowTest = () => {
|
||||
describe('Store owner can go through store Onboarding', () => {
|
||||
if ( IS_RETEST_MODE ) {
|
||||
it('can reset onboarding to default settings', async () => {
|
||||
await withRestApi.resetOnboarding();
|
||||
});
|
||||
|
||||
it('can reset shipping zones to default settings', async () => {
|
||||
await withRestApi.deleteAllShippingZones();
|
||||
});
|
||||
|
||||
it('can reset to default settings', async () => {
|
||||
await withRestApi.resetSettingsGroupToDefault('general');
|
||||
await withRestApi.resetSettingsGroupToDefault('products');
|
||||
await withRestApi.resetSettingsGroupToDefault('tax');
|
||||
});
|
||||
}
|
||||
|
||||
it('can start and complete onboarding when visiting the site for the first time.', async () => {
|
||||
await completeOnboardingWizard();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const runTaskListTest = () => {
|
||||
describe('Store owner can go through setup Task List', () => {
|
||||
it('can setup shipping', async () => {
|
||||
await page.evaluate(() => {
|
||||
document.querySelector('.woocommerce-list__item-title').scrollIntoView();
|
||||
});
|
||||
// Query for all tasks on the list
|
||||
const taskListItems = await page.$$('.woocommerce-list__item-title');
|
||||
expect(taskListItems.length).toBeInRange( 5, 6 );
|
||||
|
||||
// Work around for https://github.com/woocommerce/woocommerce-admin/issues/6761
|
||||
if ( taskListItems.length == 6 ) {
|
||||
// Click on "Set up shipping" task to move to the next step
|
||||
const [ setupTaskListItem ] = await page.$x( '//div[contains(text(),"Set up shipping")]' );
|
||||
await setupTaskListItem.click();
|
||||
|
||||
// Wait for "Proceed" button to become active
|
||||
await page.waitForSelector('button.is-primary:not(:disabled)');
|
||||
await page.waitFor(3000);
|
||||
|
||||
// Click on "Proceed" button to save shipping settings
|
||||
await page.click('button.is-primary');
|
||||
await page.waitFor(3000);
|
||||
} else {
|
||||
await merchant.openNewShipping();
|
||||
await addShippingZoneAndMethod(shippingZoneNameUS);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
runOnboardingFlowTest,
|
||||
runTaskListTest,
|
||||
};
|
|
@ -0,0 +1,47 @@
|
|||
/* eslint-disable jest/no-export, jest/no-disabled-tests */
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
const {
|
||||
merchant,
|
||||
} = require( '@woocommerce/e2e-utils' );
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
const {
|
||||
it,
|
||||
describe,
|
||||
beforeAll,
|
||||
} = require( '@jest/globals' );
|
||||
|
||||
const runInitiateWccomConnectionTest = () => {
|
||||
describe('Merchant > Initiate WCCOM Connection', () => {
|
||||
beforeAll(async () => {
|
||||
await merchant.login();
|
||||
});
|
||||
|
||||
it('can initiate WCCOM connection', async () => {
|
||||
await merchant.openExtensions();
|
||||
|
||||
// Click on a tab to choose WooCommerce Subscriptions extension
|
||||
await Promise.all([
|
||||
expect(page).toClick('a.nav-tab', {text: "WooCommerce.com Subscriptions"}),
|
||||
page.waitForNavigation({waitUntil: 'networkidle0'}),
|
||||
]);
|
||||
|
||||
// Click on Connect button to initiate a WCCOM connection
|
||||
await Promise.all([
|
||||
expect(page).toClick('.button-helper-connect'),
|
||||
page.waitForNavigation({waitUntil: 'networkidle0'}),
|
||||
]);
|
||||
|
||||
// Verify that you see a login page for connecting WCCOM account
|
||||
await expect(page).toMatchElement('div.login');
|
||||
await expect(page).toMatchElement('input#usernameOrEmail');
|
||||
await expect(page).toMatchElement('button.button', {text: "Continue"});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = runInitiateWccomConnectionTest;
|
|
@ -27,11 +27,11 @@ const runOrderApplyCouponTest = () => {
|
|||
describe('WooCommerce Orders > Apply coupon', () => {
|
||||
beforeAll(async () => {
|
||||
await merchant.login();
|
||||
await createSimpleProduct();
|
||||
couponCode = await createCoupon();
|
||||
orderId = await createSimpleOrder('Pending payment', simpleProductName);
|
||||
await Promise.all([
|
||||
await createSimpleProduct(),
|
||||
couponCode = await createCoupon(),
|
||||
orderId = await createSimpleOrder('Pending payment', simpleProductName),
|
||||
await addProductToOrder(orderId, simpleProductName),
|
||||
addProductToOrder(orderId, simpleProductName),
|
||||
|
||||
// We need to remove any listeners on the `dialog` event otherwise we can't catch the dialog below
|
||||
page.removeAllListeners('dialog'),
|
||||
|
@ -42,15 +42,14 @@ const runOrderApplyCouponTest = () => {
|
|||
} );
|
||||
|
||||
it('can apply a coupon', async () => {
|
||||
await page.waitForSelector('button.add-coupon');
|
||||
const couponDialog = await expect(page).toDisplayDialog(async () => {
|
||||
await expect(page).toClick('button.add-coupon');
|
||||
await evalAndClick('button.add-coupon');
|
||||
});
|
||||
|
||||
expect(couponDialog.message()).toMatch(couponDialogMessage);
|
||||
|
||||
// Accept the dialog with the coupon code
|
||||
await couponDialog.accept(couponCode);
|
||||
|
||||
await uiUnblocked();
|
||||
|
||||
// Verify the coupon list is showing
|
||||
|
|
|
@ -4,12 +4,186 @@
|
|||
*/
|
||||
const {
|
||||
merchant,
|
||||
verifyPublishAndTrash
|
||||
} = require( '@woocommerce/e2e-utils' );
|
||||
verifyPublishAndTrash,
|
||||
uiUnblocked
|
||||
} = require('@woocommerce/e2e-utils');
|
||||
const config = require('config');
|
||||
const {
|
||||
HTTPClientFactory,
|
||||
VariableProduct,
|
||||
GroupedProduct,
|
||||
SimpleProduct,
|
||||
ProductVariation,
|
||||
ExternalProduct
|
||||
} = require('@woocommerce/api');
|
||||
|
||||
const taxRates = [
|
||||
{
|
||||
name: 'Tax Rate Simple',
|
||||
rate: '10',
|
||||
class: 'tax-class-simple'
|
||||
},
|
||||
{
|
||||
name: 'Tax Rate Variable',
|
||||
rate: '20',
|
||||
class: 'tax-class-variable'
|
||||
},
|
||||
{
|
||||
name: 'Tax Rate External',
|
||||
rate: '30',
|
||||
class: 'tax-class-external'
|
||||
}
|
||||
];
|
||||
|
||||
const taxTotals = ['$10.00', '$40.00', '$240.00'];
|
||||
|
||||
const initProducts = async () => {
|
||||
const apiUrl = config.get('url');
|
||||
const adminUsername = config.get('users.admin.username');
|
||||
const adminPassword = config.get('users.admin.password');
|
||||
const httpClient = HTTPClientFactory.build(apiUrl)
|
||||
.withBasicAuth(adminUsername, adminPassword)
|
||||
.create();
|
||||
const taxClassesPath = '/wc/v3/taxes/classes';
|
||||
const taxClasses = [
|
||||
{
|
||||
name: 'Tax Class Simple',
|
||||
slug: 'tax-class-simple-698962'
|
||||
},
|
||||
{
|
||||
name: 'Tax Class Variable',
|
||||
slug: 'tax-class-variable-790238'
|
||||
},
|
||||
{
|
||||
name: 'Tax Class External',
|
||||
slug: 'tax-class-external-991321'
|
||||
}
|
||||
];
|
||||
|
||||
// Enable taxes in settings
|
||||
const enableTaxes = async () => {
|
||||
const path = '/wc/v3/settings/general/woocommerce_calc_taxes';
|
||||
const data = {
|
||||
value: 'yes'
|
||||
};
|
||||
await httpClient.put(path, data);
|
||||
};
|
||||
await enableTaxes();
|
||||
|
||||
// Initialize tax classes
|
||||
const initTaxClasses = async () => {
|
||||
for (const classToBeAdded of taxClasses) {
|
||||
await httpClient.post(taxClassesPath, classToBeAdded);
|
||||
}
|
||||
};
|
||||
await initTaxClasses();
|
||||
|
||||
// Initialize tax rates
|
||||
const initTaxRates = async () => {
|
||||
const path = '/wc/v3/taxes';
|
||||
|
||||
for (const rateToBeAdded of taxRates) {
|
||||
await httpClient.post(path, rateToBeAdded);
|
||||
}
|
||||
};
|
||||
await initTaxRates();
|
||||
|
||||
// Initialization functions per product type
|
||||
const initSimpleProduct = async () => {
|
||||
const repo = SimpleProduct.restRepository(httpClient);
|
||||
const simpleProduct = {
|
||||
name: 'Simple Product 273722',
|
||||
regularPrice: '100',
|
||||
taxClass: 'Tax Class Simple'
|
||||
};
|
||||
return await repo.create(simpleProduct);
|
||||
};
|
||||
const initVariableProduct = async () => {
|
||||
const variations = [
|
||||
{
|
||||
regularPrice: '200',
|
||||
attributes: [
|
||||
{
|
||||
name: 'Size',
|
||||
option: 'Small'
|
||||
},
|
||||
{
|
||||
name: 'Colour',
|
||||
option: 'Yellow'
|
||||
}
|
||||
],
|
||||
taxClass: 'Tax Class Variable'
|
||||
},
|
||||
{
|
||||
regularPrice: '300',
|
||||
attributes: [
|
||||
{
|
||||
name: 'Size',
|
||||
option: 'Medium'
|
||||
},
|
||||
{
|
||||
name: 'Colour',
|
||||
option: 'Magenta'
|
||||
}
|
||||
],
|
||||
taxClass: 'Tax Class Variable'
|
||||
}
|
||||
];
|
||||
const variableProductData = {
|
||||
name: 'Variable Product 024611',
|
||||
type: 'variable',
|
||||
taxClass: 'Tax Class Variable'
|
||||
};
|
||||
|
||||
const variationRepo = ProductVariation.restRepository(httpClient);
|
||||
const productRepo = VariableProduct.restRepository(httpClient);
|
||||
const variableProduct = await productRepo.create(variableProductData);
|
||||
for (const v of variations) {
|
||||
await variationRepo.create(variableProduct.id, v);
|
||||
}
|
||||
|
||||
return variableProduct;
|
||||
};
|
||||
const initGroupedProduct = async () => {
|
||||
const groupedRepo = GroupedProduct.restRepository(httpClient);
|
||||
const defaultGroupedData = config.get('products.grouped');
|
||||
const groupedProductData = {
|
||||
...defaultGroupedData,
|
||||
name: 'Grouped Product 858012'
|
||||
};
|
||||
|
||||
return await groupedRepo.create(groupedProductData);
|
||||
};
|
||||
const initExternalProduct = async () => {
|
||||
const repo = ExternalProduct.restRepository(httpClient);
|
||||
const defaultProps = config.get('products.external');
|
||||
const props = {
|
||||
...defaultProps,
|
||||
name: 'External product 786794',
|
||||
regularPrice: '800',
|
||||
taxClass: 'Tax Class External'
|
||||
};
|
||||
return await repo.create(props);
|
||||
};
|
||||
|
||||
// Create a product for each product type
|
||||
const simpleProduct = await initSimpleProduct();
|
||||
const variableProduct = await initVariableProduct();
|
||||
const groupedProduct = await initGroupedProduct();
|
||||
const externalProduct = await initExternalProduct();
|
||||
|
||||
return [simpleProduct, variableProduct, groupedProduct, externalProduct];
|
||||
};
|
||||
|
||||
let products;
|
||||
|
||||
const runCreateOrderTest = () => {
|
||||
describe('WooCommerce Orders > Add new order', () => {
|
||||
beforeAll(async () => {
|
||||
// Initialize products for each product type
|
||||
products = await initProducts();
|
||||
|
||||
// Login
|
||||
await merchant.login();
|
||||
});
|
||||
|
||||
|
@ -34,7 +208,75 @@ const runCreateOrderTest = () => {
|
|||
'1 order moved to the Trash.'
|
||||
);
|
||||
});
|
||||
|
||||
it('can create new complex order with multiple product types & tax classes', async () => {
|
||||
// Go to "add order" page
|
||||
await merchant.openNewOrder();
|
||||
|
||||
// Open modal window for adding line items
|
||||
await expect(page).toClick('button.add-line-item');
|
||||
await expect(page).toClick('button.add-order-item');
|
||||
await page.waitForSelector('.wc-backbone-modal-header');
|
||||
|
||||
// Search for each product to add, then verify that they are saved
|
||||
for (const { name } of products) {
|
||||
await expect(page).toClick(
|
||||
'.wc-backbone-modal-content tr:last-child .select2-selection__arrow'
|
||||
);
|
||||
await expect(page).toFill(
|
||||
'#wc-backbone-modal-dialog + .select2-container .select2-search__field',
|
||||
name
|
||||
);
|
||||
const firstResult = await page.waitForSelector(
|
||||
'li[data-selected]'
|
||||
);
|
||||
await firstResult.click();
|
||||
await expect(page).toMatchElement(
|
||||
'.wc-backbone-modal-content tr:nth-last-child(2) .wc-product-search option',
|
||||
name
|
||||
);
|
||||
}
|
||||
|
||||
// Save the line items
|
||||
await expect(page).toClick('.wc-backbone-modal-content #btn-ok');
|
||||
await uiUnblocked();
|
||||
|
||||
// Recalculate taxes
|
||||
await expect(page).toDisplayDialog(async () => {
|
||||
await expect(page).toClick('.calculate-action');
|
||||
});
|
||||
await page.waitForSelector('th.line_tax');
|
||||
|
||||
// Save the order and verify line items
|
||||
await expect(page).toClick('button.save_order');
|
||||
await page.waitForNavigation();
|
||||
for (const { name } of products) {
|
||||
await expect(page).toMatchElement('.wc-order-item-name', {
|
||||
text: name
|
||||
});
|
||||
}
|
||||
|
||||
// Verify that the names of each tax class were shown
|
||||
for (const { name } of taxRates) {
|
||||
await expect(page).toMatchElement('th.line_tax', {
|
||||
text: name
|
||||
});
|
||||
await expect(page).toMatchElement('.wc-order-totals td.label', {
|
||||
text: name
|
||||
});
|
||||
}
|
||||
|
||||
// Verify tax amounts
|
||||
for (const amount of taxTotals) {
|
||||
await expect(page).toMatchElement('td.line_tax', {
|
||||
text: amount
|
||||
});
|
||||
await expect(page).toMatchElement('.wc-order-totals td.total', {
|
||||
text: amount
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = runCreateOrderTest;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable jest/no-export, jest/no-disabled-tests, */
|
||||
/* eslint-disable jest/no-export, jest/no-disabled-tests, jest/expect-expect */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -10,36 +10,80 @@ const {
|
|||
createSimpleProduct,
|
||||
addProductToOrder,
|
||||
clickUpdateOrder,
|
||||
factories,
|
||||
selectOptionInSelect2,
|
||||
} = require( '@woocommerce/e2e-utils' );
|
||||
|
||||
const searchString = 'John Doe';
|
||||
const customerBilling = {
|
||||
first_name: 'John',
|
||||
last_name: 'Doe',
|
||||
company: 'Automattic',
|
||||
country: 'US',
|
||||
address_1: 'address1',
|
||||
address_2: 'address2',
|
||||
city: 'San Francisco',
|
||||
state: 'CA',
|
||||
postcode: '94107',
|
||||
phone: '123456789',
|
||||
email: 'john.doe@example.com',
|
||||
};
|
||||
const customerShipping = {
|
||||
first_name: 'Tim',
|
||||
last_name: 'Clark',
|
||||
company: 'Automattic',
|
||||
country: 'US',
|
||||
address_1: 'Oxford Ave',
|
||||
address_2: 'Linwood Ave',
|
||||
city: 'Buffalo',
|
||||
state: 'NY',
|
||||
postcode: '14201',
|
||||
phone: '123456789',
|
||||
email: 'john.doe@example.com',
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the billing fields for the customer account for this test suite.
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const updateCustomerBilling = async () => {
|
||||
const client = factories.api.withDefaultPermalinks;
|
||||
const customerEndpoint = 'wc/v3/customers/';
|
||||
const customers = await client.get( customerEndpoint, {
|
||||
search: 'Jane',
|
||||
role: 'all',
|
||||
} );
|
||||
if ( ! customers.data | ! customers.data.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const customerId = customers.data[0].id;
|
||||
const customerData = {
|
||||
id: customerId,
|
||||
billing: customerBilling,
|
||||
shipping: customerShipping,
|
||||
};
|
||||
await client.put( customerEndpoint + customerId, customerData );
|
||||
};
|
||||
|
||||
const runOrderSearchingTest = () => {
|
||||
describe('WooCommerce Orders > Search orders', () => {
|
||||
let orderId;
|
||||
beforeAll(async () => {
|
||||
await merchant.login();
|
||||
beforeAll( async () => {
|
||||
await createSimpleProduct('Wanted Product');
|
||||
await updateCustomerBilling();
|
||||
|
||||
// Create new order for testing
|
||||
await merchant.login();
|
||||
await merchant.openNewOrder();
|
||||
await page.waitForSelector('#order_status');
|
||||
await page.click('#customer_user');
|
||||
await page.click('span.select2-search > input.select2-search__field');
|
||||
await page.type('span.select2-search > input.select2-search__field', 'Customer');
|
||||
await page.type('span.select2-search > input.select2-search__field', 'Jane Smith');
|
||||
await page.waitFor(2000); // to avoid flakyness
|
||||
await page.keyboard.press('Enter');
|
||||
|
||||
// Change the shipping data
|
||||
await page.waitFor(1000); // to avoid flakiness
|
||||
await page.click('.billing-same-as-shipping');
|
||||
await page.keyboard.press('Enter');
|
||||
await page.waitForSelector('#_shipping_first_name');
|
||||
await clearAndFillInput('#_shipping_first_name', 'Tim');
|
||||
await clearAndFillInput('#_shipping_last_name', 'Clark');
|
||||
await clearAndFillInput('#_shipping_address_1', 'Oxford Ave');
|
||||
await clearAndFillInput('#_shipping_address_2', 'Linwood Ave');
|
||||
await clearAndFillInput('#_shipping_city', 'Buffalo');
|
||||
await clearAndFillInput('#_shipping_postcode', '14201');
|
||||
|
||||
// Get the post id
|
||||
const variablePostId = await page.$('#post_ID');
|
||||
orderId = (await(await variablePostId.getProperty('value')).jsonValue());
|
||||
|
@ -53,79 +97,82 @@ const runOrderSearchingTest = () => {
|
|||
});
|
||||
|
||||
it('can search for order by order id', async () => {
|
||||
await searchForOrder(orderId, orderId, 'John Doe');
|
||||
await searchForOrder(orderId, orderId, searchString);
|
||||
});
|
||||
|
||||
it('can search for order by billing first name', async () => {
|
||||
await searchForOrder('John', orderId, 'John Doe');
|
||||
await searchForOrder(customerBilling.first_name, orderId, searchString);
|
||||
})
|
||||
|
||||
it('can search for order by billing last name', async () => {
|
||||
await searchForOrder('Doe', orderId, 'John Doe');
|
||||
await searchForOrder(customerBilling.last_name, orderId, searchString);
|
||||
})
|
||||
|
||||
it('can search for order by billing company name', async () => {
|
||||
await searchForOrder('Automattic', orderId, 'John Doe');
|
||||
await searchForOrder(customerBilling.company, orderId, searchString);
|
||||
})
|
||||
|
||||
it('can search for order by billing first address', async () => {
|
||||
await searchForOrder('addr 1', orderId, 'John Doe');
|
||||
await searchForOrder(customerBilling.address_1, orderId, searchString);
|
||||
})
|
||||
|
||||
it('can search for order by billing second address', async () => {
|
||||
await searchForOrder('addr 2', orderId, 'John Doe');
|
||||
await searchForOrder(customerBilling.address_2, orderId, searchString);
|
||||
})
|
||||
|
||||
it('can search for order by billing city name', async () => {
|
||||
await searchForOrder('San Francisco', orderId, 'John Doe');
|
||||
await searchForOrder(customerBilling.city, orderId, searchString);
|
||||
})
|
||||
|
||||
it('can search for order by billing post code', async () => {
|
||||
await searchForOrder('94107', orderId, 'John Doe');
|
||||
await searchForOrder(customerBilling.postcode, orderId, searchString);
|
||||
})
|
||||
|
||||
it('can search for order by billing email', async () => {
|
||||
await searchForOrder('john.doe@example.com', orderId, 'John Doe');
|
||||
await searchForOrder(customerBilling.email, orderId, searchString);
|
||||
})
|
||||
|
||||
it('can search for order by billing phone', async () => {
|
||||
await searchForOrder('123456789', orderId, 'John Doe');
|
||||
await searchForOrder(customerBilling.phone, orderId, searchString);
|
||||
})
|
||||
|
||||
it('can search for order by billing state', async () => {
|
||||
await searchForOrder('CA', orderId, 'John Doe');
|
||||
await searchForOrder(customerBilling.state, orderId, searchString);
|
||||
})
|
||||
|
||||
it('can search for order by shipping first name', async () => {
|
||||
await searchForOrder('Tim', orderId, 'John Doe');
|
||||
await searchForOrder(customerShipping.first_name, orderId, searchString);
|
||||
})
|
||||
|
||||
it('can search for order by shipping last name', async () => {
|
||||
await searchForOrder('Clark', orderId, 'John Doe');
|
||||
await searchForOrder(customerShipping.last_name, orderId, searchString);
|
||||
})
|
||||
|
||||
it('can search for order by shipping first address', async () => {
|
||||
await searchForOrder('Oxford Ave', orderId, 'John Doe');
|
||||
await searchForOrder(customerShipping.address_1, orderId, searchString);
|
||||
})
|
||||
|
||||
it('can search for order by shipping second address', async () => {
|
||||
await searchForOrder('Linwood Ave', orderId, 'John Doe');
|
||||
await searchForOrder(customerShipping.address_2, orderId, searchString);
|
||||
})
|
||||
|
||||
it('can search for order by shipping city name', async () => {
|
||||
await searchForOrder('Buffalo', orderId, 'John Doe');
|
||||
await searchForOrder(customerShipping.city, orderId, searchString);
|
||||
})
|
||||
|
||||
it('can search for order by shipping postcode name', async () => {
|
||||
await searchForOrder('14201', orderId, 'John Doe');
|
||||
await searchForOrder(customerShipping.postcode, orderId, searchString);
|
||||
})
|
||||
|
||||
it('can search for order by shipping state name', async () => {
|
||||
await searchForOrder('CA', orderId, 'John Doe');
|
||||
/**
|
||||
* shipping state is abbreviated. This test passes if billing and shipping state are the same
|
||||
*/
|
||||
it.skip('can search for order by shipping state name', async () => {
|
||||
await searchForOrder('New York', orderId, searchString);
|
||||
})
|
||||
|
||||
it('can search for order by item name', async () => {
|
||||
await searchForOrder('Wanted Product', orderId, 'John Doe');
|
||||
await searchForOrder('Wanted Product', orderId, searchString);
|
||||
})
|
||||
});
|
||||
};
|
||||
|
|
|
@ -57,7 +57,7 @@ const runOrderStatusFiltersTest = () => {
|
|||
await createSimpleOrder(orderStatus.cancelled.description.text);
|
||||
await createSimpleOrder(orderStatus.refunded.description.text);
|
||||
await createSimpleOrder(orderStatus.failed.description.text);
|
||||
}, 40000);
|
||||
}, 60000);
|
||||
|
||||
afterAll( async () => {
|
||||
// Make sure we're on the all orders view and cleanup the orders we created
|
||||
|
|
|
@ -35,7 +35,6 @@ const runProductEditDetailsTest = () => {
|
|||
await expect(page).toFill('#_regular_price', '100.05');
|
||||
|
||||
// Save the changes
|
||||
await expect(page).toClick('#publish');
|
||||
await verifyAndPublish('Product updated.');
|
||||
await uiUnblocked();
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ const runImportProductsTest = () => {
|
|||
await expect(page).toClick('button[value="Run the importer"]');
|
||||
|
||||
// Waiting for importer to finish
|
||||
await page.waitForSelector('section.woocommerce-importer-done', {visible:true, timeout: 60000});
|
||||
await page.waitForSelector('section.woocommerce-importer-done', {visible:true, timeout: 120000});
|
||||
await page.waitForSelector('.woocommerce-importer-done');
|
||||
await expect(page).toMatchElement('.woocommerce-importer-done', {text: 'Import complete!'});
|
||||
});
|
||||
|
|
|
@ -3,11 +3,12 @@
|
|||
* Internal dependencies
|
||||
*/
|
||||
const {
|
||||
shopper,
|
||||
merchant,
|
||||
clickTab,
|
||||
uiUnblocked,
|
||||
evalAndClick,
|
||||
setCheckbox,
|
||||
verifyAndPublish,
|
||||
} = require( '@woocommerce/e2e-utils' );
|
||||
const {
|
||||
waitAndClick,
|
||||
|
@ -24,7 +25,8 @@ const {
|
|||
} = require( '@jest/globals' );
|
||||
const config = require( 'config' );
|
||||
|
||||
const simpleProductName = config.get( 'products.simple.name' );
|
||||
const VirtualProductName = 'Virtual Product Name';
|
||||
const NonVirtualProductName = 'Non-Virtual Product Name';
|
||||
const simpleProductPrice = config.has('products.simple.price') ? config.get('products.simple.price') : '9.99';
|
||||
|
||||
const verifyPublishAndTrash = async () => {
|
||||
|
@ -61,35 +63,74 @@ const runAddSimpleProductTest = () => {
|
|||
await merchant.login();
|
||||
});
|
||||
|
||||
it('can create simple virtual product titled "Simple Product" with regular price $9.99', async () => {
|
||||
it('can create simple virtual product and add it to the cart', async () => {
|
||||
await openNewProductAndVerify();
|
||||
|
||||
// Set product data
|
||||
await expect(page).toFill('#title', simpleProductName);
|
||||
// Set product data and publish the product
|
||||
await expect(page).toFill('#title', VirtualProductName);
|
||||
await expect(page).toClick('#_virtual');
|
||||
await clickTab('General');
|
||||
await expect(page).toFill('#_regular_price', simpleProductPrice);
|
||||
await verifyAndPublish();
|
||||
|
||||
// Publish product, verify that it was published. Trash product, verify that it was trashed.
|
||||
await verifyPublishAndTrash(
|
||||
'#publish',
|
||||
'.updated.notice',
|
||||
'Product published.',
|
||||
'Move to Trash',
|
||||
'1 product moved to the Trash.'
|
||||
);
|
||||
await merchant.logout();
|
||||
|
||||
// See product in the shop and add it to the cart
|
||||
await shopper.goToShop();
|
||||
await shopper.addToCartFromShopPage(VirtualProductName);
|
||||
await shopper.goToCart();
|
||||
await shopper.productIsInCart(VirtualProductName);
|
||||
|
||||
// Assert that the page does not contain shipping calculation button
|
||||
await expect(page).not.toMatchElement('a.shipping-calculator-button');
|
||||
|
||||
// Remove product from cart
|
||||
await shopper.removeFromCart(VirtualProductName);
|
||||
});
|
||||
|
||||
it('can create simple non-virtual product and add it to the cart', async () => {
|
||||
await merchant.login();
|
||||
await openNewProductAndVerify();
|
||||
|
||||
// Set product data and publish the product
|
||||
await expect(page).toFill('#title', NonVirtualProductName);
|
||||
await clickTab('General');
|
||||
await expect(page).toFill('#_regular_price', simpleProductPrice);
|
||||
await verifyAndPublish();
|
||||
|
||||
await merchant.logout();
|
||||
|
||||
// See product in the shop and add it to the cart
|
||||
await shopper.goToShop();
|
||||
await shopper.addToCartFromShopPage(NonVirtualProductName);
|
||||
await shopper.goToCart();
|
||||
await shopper.productIsInCart(NonVirtualProductName);
|
||||
|
||||
// Assert that the page does contain shipping calculation button
|
||||
await page.waitForSelector('a.shipping-calculator-button');
|
||||
await expect(page).toMatchElement('a.shipping-calculator-button');
|
||||
|
||||
// Remove product from cart
|
||||
await shopper.removeFromCart(NonVirtualProductName);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const runAddVariableProductTest = () => {
|
||||
describe('Add New Variable Product Page', () => {
|
||||
beforeAll(async () => {
|
||||
await merchant.login();
|
||||
});
|
||||
|
||||
it('can create product with variations', async () => {
|
||||
await openNewProductAndVerify();
|
||||
|
||||
// Set product data
|
||||
await expect(page).toFill('#title', 'Variable Product with Three Variations');
|
||||
await expect(page).toSelect('#product-type', 'Variable product');
|
||||
});
|
||||
|
||||
it('can create set variable product attributes', async () => {
|
||||
|
||||
// Create attributes for variations
|
||||
await waitAndClick( page, '.attribute_tab a' );
|
||||
|
@ -111,7 +152,9 @@ const runAddVariableProductTest = () => {
|
|||
// Wait for attribute form to save (triggers 2 UI blocks)
|
||||
await uiUnblocked();
|
||||
await uiUnblocked();
|
||||
});
|
||||
|
||||
it('can create variable product variations', async () => {
|
||||
// Create variations from attributes
|
||||
await waitForSelector( page, '.variations_tab' );
|
||||
await waitAndClick( page, '.variations_tab a' );
|
||||
|
@ -129,8 +172,11 @@ const runAddVariableProductTest = () => {
|
|||
// Set some variation data
|
||||
await uiUnblocked();
|
||||
await uiUnblocked();
|
||||
});
|
||||
|
||||
it('can create variation attributes', async () => {
|
||||
await waitAndClick( page, '.variations_tab a' );
|
||||
await uiUnblocked();
|
||||
await waitForSelector(
|
||||
page,
|
||||
'select[name="attribute_attr-1[0]"]',
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
/* eslint-disable jest/no-export*/
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
const { merchant } = require('@woocommerce/e2e-utils');
|
||||
const { lorem, helpers } = require('faker');
|
||||
|
||||
const runAddShippingClassesTest = () => {
|
||||
describe('Merchant can add shipping classes', () => {
|
||||
beforeAll(async () => {
|
||||
await merchant.login();
|
||||
|
||||
// Go to Shipping Classes page
|
||||
await merchant.openSettings('shipping', 'classes');
|
||||
});
|
||||
|
||||
it('can add shipping classes', async () => {
|
||||
const shippingClassSlug = {
|
||||
name: lorem.words(),
|
||||
slug: lorem.slug(),
|
||||
description: lorem.sentence()
|
||||
};
|
||||
const shippingClassNoSlug = {
|
||||
name: lorem.words(3),
|
||||
slug: '',
|
||||
description: lorem.sentence()
|
||||
};
|
||||
const shippingClasses = [shippingClassSlug, shippingClassNoSlug];
|
||||
|
||||
// Add shipping classes
|
||||
for (const { name, slug, description } of shippingClasses) {
|
||||
await expect(page).toClick('.wc-shipping-class-add');
|
||||
await expect(page).toFill(
|
||||
'.editing:last-child [data-attribute="name"]',
|
||||
name
|
||||
);
|
||||
await expect(page).toFill(
|
||||
'.editing:last-child [data-attribute="slug"]',
|
||||
slug
|
||||
);
|
||||
await expect(page).toFill(
|
||||
'.editing:last-child [data-attribute="description"]',
|
||||
description
|
||||
);
|
||||
}
|
||||
await expect(page).toClick('.wc-shipping-class-save');
|
||||
|
||||
// Set the expected auto-generated slug
|
||||
shippingClassNoSlug.slug = helpers.slugify(
|
||||
shippingClassNoSlug.name
|
||||
);
|
||||
|
||||
// Verify that the specified shipping classes were saved
|
||||
for (const { name, slug, description } of shippingClasses) {
|
||||
const row = await expect(
|
||||
page
|
||||
).toMatchElement('.wc-shipping-class-rows tr', { text: slug });
|
||||
|
||||
await expect(row).toMatchElement(
|
||||
'.wc-shipping-class-name',
|
||||
name
|
||||
);
|
||||
await expect(row).toMatchElement(
|
||||
'.wc-shipping-class-description',
|
||||
description
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = runAddShippingClassesTest;
|
|
@ -10,10 +10,18 @@ const {
|
|||
addShippingZoneAndMethod,
|
||||
clearAndFillInput,
|
||||
selectOptionInSelect2,
|
||||
evalAndClick,
|
||||
uiUnblocked,
|
||||
deleteAllShippingZones,
|
||||
} = require( '@woocommerce/e2e-utils' );
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
const {
|
||||
it,
|
||||
describe,
|
||||
beforeAll,
|
||||
} = require( '@jest/globals' );
|
||||
|
||||
const config = require( 'config' );
|
||||
const simpleProductPrice = config.has( 'products.simple.price' ) ? config.get( 'products.simple.price' ) : '9.99';
|
||||
const simpleProductName = config.get( 'products.simple.name' );
|
||||
|
@ -28,27 +36,7 @@ const runAddNewShippingZoneTest = () => {
|
|||
beforeAll(async () => {
|
||||
await merchant.login();
|
||||
await createSimpleProduct();
|
||||
await merchant.openSettings('shipping');
|
||||
|
||||
// Delete existing shipping zones.
|
||||
try {
|
||||
let zone = await page.$( '.wc-shipping-zone-delete' );
|
||||
if ( zone ) {
|
||||
// WP action links aren't clickable because they are hidden with a left=-9999 style.
|
||||
await page.evaluate(() => {
|
||||
document.querySelector('.wc-shipping-zone-name .row-actions')
|
||||
.style
|
||||
.left = '0';
|
||||
});
|
||||
while ( zone ) {
|
||||
await evalAndClick( '.wc-shipping-zone-delete' );
|
||||
await uiUnblocked();
|
||||
zone = await page.$( '.wc-shipping-zone-delete' );
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Prevent an error here causing the test to fail.
|
||||
}
|
||||
await deleteAllShippingZones();
|
||||
});
|
||||
|
||||
it('add shipping zone for San Francisco with free Local pickup', async () => {
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
/* eslint-disable jest/no-export, jest/no-disabled-tests, jest/expect-expect */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
const {
|
||||
shopper,
|
||||
merchant,
|
||||
createSimpleProduct,
|
||||
addShippingZoneAndMethod,
|
||||
clearAndFillInput,
|
||||
uiUnblocked,
|
||||
selectOptionInSelect2,
|
||||
} = require( '@woocommerce/e2e-utils' );
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
const {
|
||||
it,
|
||||
describe,
|
||||
beforeAll,
|
||||
} = require( '@jest/globals' );
|
||||
|
||||
const config = require( 'config' );
|
||||
const firstProductPrice = config.has( 'products.simple.price' ) ? config.get( 'products.simple.price' ) : '9.99';
|
||||
const secondProductPrice = '4.99';
|
||||
const fourProductPrice = firstProductPrice * 4;
|
||||
var twoProductsPrice = (+firstProductPrice) + (+secondProductPrice);
|
||||
var firstProductPriceWithFlatRate = (+firstProductPrice) + (+5);
|
||||
var fourProductPriceWithFlatRate = (+fourProductPrice) + (+5);
|
||||
var twoProductsPriceWithFlatRate = (+twoProductsPrice) + (+5);
|
||||
const firstProductName = 'First Product';
|
||||
const secondProductName = 'Second Product';
|
||||
const shippingZoneNameDE = 'Germany Free Shipping';
|
||||
const shippingCountryDE = 'country:DE';
|
||||
const shippingZoneNameFR = 'France Flat Local';
|
||||
const shippingCountryFR = 'country:FR';
|
||||
|
||||
const runCartCalculateShippingTest = () => {
|
||||
describe('Cart Calculate Shipping', () => {
|
||||
beforeAll(async () => {
|
||||
await merchant.login();
|
||||
await createSimpleProduct(firstProductName);
|
||||
await createSimpleProduct(secondProductName, secondProductPrice);
|
||||
await merchant.openNewShipping();
|
||||
|
||||
// Add a new shipping zone Germany with Free shipping
|
||||
await addShippingZoneAndMethod(shippingZoneNameDE, shippingCountryDE, ' ', 'free_shipping');
|
||||
|
||||
// Add a new shipping zone for France with Flat rate & Local pickup
|
||||
await addShippingZoneAndMethod(shippingZoneNameFR, shippingCountryFR, ' ', 'flat_rate');
|
||||
await page.waitFor(1000); // to avoid flakiness in headless
|
||||
await page.click('a.wc-shipping-zone-method-settings', {text: 'Flat rate'});
|
||||
await clearAndFillInput('#woocommerce_flat_rate_cost', '5');
|
||||
await page.click('.wc-backbone-modal-main button#btn-ok');
|
||||
// Add additional method Local pickup for the same location
|
||||
await page.waitFor(1000); // to avoid flakiness in headless
|
||||
await page.click('button.wc-shipping-zone-add-method', {text:'Add shipping method'});
|
||||
await page.waitForSelector('.wc-shipping-zone-method-selector');
|
||||
await page.select('select[name="add_method_id"]', 'local_pickup');
|
||||
await page.click('button#btn-ok');
|
||||
await page.waitForSelector('#zone_locations');
|
||||
await merchant.logout();
|
||||
await shopper.emptyCart();
|
||||
});
|
||||
|
||||
it('allows customer to calculate Free Shipping if in Germany', async () => {
|
||||
await shopper.goToShop();
|
||||
await shopper.addToCartFromShopPage(firstProductName);
|
||||
await shopper.goToCart();
|
||||
|
||||
// Set shipping country to Germany
|
||||
await expect(page).toClick('a.shipping-calculator-button');
|
||||
await expect(page).toClick('#select2-calc_shipping_country-container');
|
||||
await selectOptionInSelect2('Germany');
|
||||
await expect(page).toClick('button[name="calc_shipping"]');
|
||||
|
||||
// Verify shipping costs
|
||||
await page.waitForSelector('.order-total');
|
||||
await expect(page).toMatchElement('.shipping ul#shipping_method > li', {text: 'Free shipping'});
|
||||
await expect(page).toMatchElement('.order-total .amount', {text: `$${firstProductPrice}`});
|
||||
});
|
||||
|
||||
it('allows customer to calculate Flat rate and Local pickup if in France', async () => {
|
||||
await page.reload();
|
||||
|
||||
// Set shipping country to France
|
||||
await expect(page).toClick('a.shipping-calculator-button');
|
||||
await expect(page).toClick('#select2-calc_shipping_country-container');
|
||||
await selectOptionInSelect2('France');
|
||||
await expect(page).toClick('button[name="calc_shipping"]');
|
||||
|
||||
// Verify shipping costs
|
||||
await page.waitForSelector('.order-total');
|
||||
await expect(page).toMatchElement('.shipping .amount', {text: '$5.00'});
|
||||
await expect(page).toMatchElement('.order-total .amount', {text: `$${firstProductPriceWithFlatRate}`});
|
||||
});
|
||||
|
||||
it('should show correct total cart price after updating quantity', async () => {
|
||||
await shopper.setCartQuantity(firstProductName, 4);
|
||||
await expect(page).toClick('button', {text: 'Update cart'});
|
||||
await uiUnblocked();
|
||||
await expect(page).toMatchElement('.order-total .amount', {text: `$${fourProductPriceWithFlatRate}`});
|
||||
});
|
||||
|
||||
it('should show correct total cart price with 2 products and flat rate', async () => {
|
||||
await shopper.goToShop();
|
||||
await shopper.addToCartFromShopPage(secondProductName);
|
||||
await shopper.goToCart();
|
||||
|
||||
await shopper.setCartQuantity(firstProductName, 1);
|
||||
await expect(page).toClick('button', {text: 'Update cart'});
|
||||
await uiUnblocked();
|
||||
await page.waitForSelector('.order-total');
|
||||
await expect(page).toMatchElement('.shipping .amount', {text: '$5.00'});
|
||||
await expect(page).toMatchElement('.order-total .amount', {text: `$${twoProductsPriceWithFlatRate}`});
|
||||
});
|
||||
|
||||
it('should show correct total cart price with 2 products without flat rate', async () => {
|
||||
await page.reload();
|
||||
|
||||
// Set shipping country to Spain
|
||||
await expect(page).toClick('a.shipping-calculator-button');
|
||||
await expect(page).toClick('#select2-calc_shipping_country-container');
|
||||
await selectOptionInSelect2('Spain');
|
||||
await expect(page).toClick('button[name="calc_shipping"]');
|
||||
|
||||
// Verify shipping costs
|
||||
await page.waitForSelector('.order-total');
|
||||
await expect(page).toMatchElement('.order-total .amount', {text: `$${twoProductsPrice}`});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = runCartCalculateShippingTest;
|
|
@ -41,7 +41,7 @@ const runCartApplyCouponsTest = () => {
|
|||
await shopper.goToCart();
|
||||
});
|
||||
|
||||
it('allows customer to apply fixed cart coupon', async () => {
|
||||
it('allows cart to apply fixed cart coupon', async () => {
|
||||
await applyCoupon(couponFixedCart);
|
||||
await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
|
||||
|
@ -52,7 +52,7 @@ const runCartApplyCouponsTest = () => {
|
|||
await removeCoupon(couponFixedCart);
|
||||
});
|
||||
|
||||
it('allows customer to apply percentage coupon', async () => {
|
||||
it('allows cart to apply percentage coupon', async () => {
|
||||
await applyCoupon(couponPercentage);
|
||||
await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
|
||||
|
@ -63,7 +63,7 @@ const runCartApplyCouponsTest = () => {
|
|||
await removeCoupon(couponPercentage);
|
||||
});
|
||||
|
||||
it('allows customer to apply fixed product coupon', async () => {
|
||||
it('allows cart to apply fixed product coupon', async () => {
|
||||
await applyCoupon(couponFixedProduct);
|
||||
await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
|
||||
|
@ -74,7 +74,7 @@ const runCartApplyCouponsTest = () => {
|
|||
await removeCoupon(couponFixedProduct);
|
||||
});
|
||||
|
||||
it('prevents customer applying same coupon twice', async () => {
|
||||
it('prevents cart applying same coupon twice', async () => {
|
||||
await applyCoupon(couponFixedCart);
|
||||
await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
await applyCoupon(couponFixedCart);
|
||||
|
@ -84,7 +84,7 @@ const runCartApplyCouponsTest = () => {
|
|||
await expect(page).toMatchElement('.order-total .amount', {text: '$4.99'});
|
||||
});
|
||||
|
||||
it('allows customer to apply multiple coupons', async () => {
|
||||
it('allows cart to apply multiple coupons', async () => {
|
||||
await applyCoupon(couponFixedProduct);
|
||||
await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/* eslint-disable jest/no-export, jest/no-disabled-tests, jest/expect-expect */
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
const {
|
||||
shopper,
|
||||
merchant,
|
||||
createSimpleProduct,
|
||||
setCheckbox,
|
||||
unsetCheckbox,
|
||||
settingsPageSaveChanges,
|
||||
} = require( '@woocommerce/e2e-utils' );
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
const {
|
||||
it,
|
||||
describe,
|
||||
beforeAll,
|
||||
afterAll,
|
||||
} = require( '@jest/globals' );
|
||||
|
||||
const config = require( 'config' );
|
||||
const simpleProductName = config.get( 'products.simple.name' );
|
||||
|
||||
const runCartRedirectionTest = () => {
|
||||
describe('Cart > Redirect to cart from shop', () => {
|
||||
let simplePostIdValue;
|
||||
beforeAll(async () => {
|
||||
await merchant.login();
|
||||
simplePostIdValue = await createSimpleProduct();
|
||||
|
||||
// Set checkbox in settings to enable cart redirection
|
||||
await merchant.openSettings('products');
|
||||
await setCheckbox('#woocommerce_cart_redirect_after_add');
|
||||
await settingsPageSaveChanges();
|
||||
|
||||
await merchant.logout();
|
||||
});
|
||||
|
||||
it('can redirect user to cart from shop page', async () => {
|
||||
await shopper.goToShop();
|
||||
|
||||
// Add to cart from shop page
|
||||
const addToCartXPath = `//li[contains(@class, "type-product") and a/h2[contains(text(), "${ simpleProductName }")]]` +
|
||||
'//a[contains(@class, "add_to_cart_button") and contains(@class, "ajax_add_to_cart")';
|
||||
const [ addToCartButton ] = await page.$x( addToCartXPath + ']' );
|
||||
addToCartButton.click();
|
||||
await page.waitFor(1000); // to avoid flakiness
|
||||
|
||||
await shopper.productIsInCart(simpleProductName);
|
||||
await shopper.removeFromCart(simpleProductName);
|
||||
});
|
||||
|
||||
it('can redirect user to cart from detail page', async () => {
|
||||
await shopper.goToProduct(simplePostIdValue);
|
||||
|
||||
// Add to cart from detail page
|
||||
await shopper.addToCart();
|
||||
await page.waitFor(1000); // to avoid flakiness
|
||||
|
||||
await shopper.productIsInCart(simpleProductName);
|
||||
await shopper.removeFromCart(simpleProductName);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await merchant.login();
|
||||
await merchant.openSettings('products');
|
||||
await unsetCheckbox('#woocommerce_cart_redirect_after_add');
|
||||
await settingsPageSaveChanges();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = runCartRedirectionTest;
|
|
@ -36,7 +36,7 @@ const runCartPageTest = () => {
|
|||
await expect(page).toMatchElement('.cart-empty', {text: 'Your cart is currently empty.'});
|
||||
});
|
||||
|
||||
it('should add the product to the cart when "Add to cart" is clicked', async () => {
|
||||
it('should add the product to the cart from the shop page', async () => {
|
||||
await shopper.goToShop();
|
||||
await shopper.addToCartFromShopPage(simpleProductName);
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ const runCheckoutApplyCouponsTest = () => {
|
|||
await shopper.goToCheckout();
|
||||
});
|
||||
|
||||
it('allows customer to apply fixed cart coupon', async () => {
|
||||
it('allows checkout to apply fixed cart coupon', async () => {
|
||||
await applyCoupon(couponFixedCart);
|
||||
await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
|
||||
|
@ -54,7 +54,7 @@ const runCheckoutApplyCouponsTest = () => {
|
|||
await removeCoupon(couponFixedCart);
|
||||
});
|
||||
|
||||
it('allows customer to apply percentage coupon', async () => {
|
||||
it('allows checkout to apply percentage coupon', async () => {
|
||||
await applyCoupon(couponPercentage);
|
||||
await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
|
||||
|
@ -64,7 +64,7 @@ const runCheckoutApplyCouponsTest = () => {
|
|||
await removeCoupon(couponPercentage);
|
||||
});
|
||||
|
||||
it('allows customer to apply fixed product coupon', async () => {
|
||||
it('allows checkout to apply fixed product coupon', async () => {
|
||||
await applyCoupon(couponFixedProduct);
|
||||
await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
|
||||
|
@ -75,7 +75,7 @@ const runCheckoutApplyCouponsTest = () => {
|
|||
await removeCoupon(couponFixedProduct);
|
||||
});
|
||||
|
||||
it('prevents customer applying same coupon twice', async () => {
|
||||
it('prevents checkout applying same coupon twice', async () => {
|
||||
await applyCoupon(couponFixedCart);
|
||||
await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
await applyCoupon(couponFixedCart);
|
||||
|
@ -85,7 +85,7 @@ const runCheckoutApplyCouponsTest = () => {
|
|||
await expect(page).toMatchElement('.order-total .amount', {text: '$4.99'});
|
||||
});
|
||||
|
||||
it('allows customer to apply multiple coupons', async () => {
|
||||
it('allows checkout to apply multiple coupons', async () => {
|
||||
await applyCoupon(couponFixedProduct);
|
||||
await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
|
||||
|
@ -94,7 +94,7 @@ const runCheckoutApplyCouponsTest = () => {
|
|||
await expect(page).toMatchElement('.order-total .amount', {text: '$0.00'});
|
||||
});
|
||||
|
||||
it('restores cart total when coupons are removed', async () => {
|
||||
it('restores checkout total when coupons are removed', async () => {
|
||||
await removeCoupon(couponFixedCart);
|
||||
await removeCoupon(couponFixedProduct);
|
||||
await expect(page).toMatchElement('.order-total .amount', {text: '$9.99'});
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
uiUnblocked,
|
||||
setCheckbox,
|
||||
settingsPageSaveChanges,
|
||||
addShippingZoneAndMethod,
|
||||
withRestApi,
|
||||
} = require( '@woocommerce/e2e-utils' );
|
||||
|
||||
/**
|
||||
|
@ -22,17 +24,23 @@ const {
|
|||
|
||||
const config = require( 'config' );
|
||||
const simpleProductName = config.get( 'products.simple.name' );
|
||||
const customerBilling = config.get( 'addresses.customer.billing' );
|
||||
|
||||
const runCheckoutCreateAccountTest = () => {
|
||||
describe('Shopper Checkout Create Account', () => {
|
||||
beforeAll(async () => {
|
||||
await merchant.login();
|
||||
await createSimpleProduct();
|
||||
await withRestApi.deleteCustomerByEmail( customerBilling.email );
|
||||
|
||||
// Set checkbox for creating an account during checkout
|
||||
await merchant.login();
|
||||
await merchant.openSettings('account');
|
||||
await setCheckbox('#woocommerce_enable_signup_and_login_from_checkout');
|
||||
await settingsPageSaveChanges();
|
||||
|
||||
// Set free shipping within California
|
||||
await addShippingZoneAndMethod('Free Shipping CA', 'state:US:CA', ' ', 'free_shipping');
|
||||
|
||||
await merchant.logout();
|
||||
|
||||
// Add simple product to cart and proceed to checkout
|
||||
|
@ -44,7 +52,7 @@ const runCheckoutCreateAccountTest = () => {
|
|||
|
||||
it('can create an account during checkout', async () => {
|
||||
// Fill all the details for a new customer
|
||||
await shopper.fillBillingDetails(config.get('addresses.customer.billing'));
|
||||
await shopper.fillBillingDetails( customerBilling );
|
||||
await uiUnblocked();
|
||||
|
||||
// Set checkbox for creating account during checkout
|
||||
|
@ -58,7 +66,7 @@ const runCheckoutCreateAccountTest = () => {
|
|||
it('can verify that the customer has been created', async () => {
|
||||
await merchant.login();
|
||||
await merchant.openAllUsersView();
|
||||
await expect(page).toMatchElement('td.email.column-email > a', {text: 'john.doe@example.com'});
|
||||
await expect(page).toMatchElement('td.email.column-email > a', { text: customerBilling.email });
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -9,7 +9,8 @@ const {
|
|||
setCheckbox,
|
||||
settingsPageSaveChanges,
|
||||
uiUnblocked,
|
||||
verifyCheckboxIsSet
|
||||
verifyCheckboxIsSet,
|
||||
addShippingZoneAndMethod,
|
||||
} = require( '@woocommerce/e2e-utils' );
|
||||
|
||||
const config = require( 'config' );
|
||||
|
@ -29,6 +30,8 @@ const runCheckoutPageTest = () => {
|
|||
await merchant.login();
|
||||
await createSimpleProduct();
|
||||
|
||||
// Set free shipping within California
|
||||
await addShippingZoneAndMethod('Free Shipping CA', 'state:US:CA', ' ', 'free_shipping');
|
||||
// Go to general settings page
|
||||
await merchant.openSettings('general');
|
||||
|
||||
|
|
|
@ -7,25 +7,29 @@ const {
|
|||
merchant,
|
||||
setCheckbox,
|
||||
settingsPageSaveChanges,
|
||||
withRestApi,
|
||||
} = require( '@woocommerce/e2e-utils' );
|
||||
|
||||
const customerEmailAddress = 'john.doe.test@example.com';
|
||||
|
||||
const runMyAccountCreateAccountTest = () => {
|
||||
describe('Shopper My Account Create Account', () => {
|
||||
beforeAll(async () => {
|
||||
await withRestApi.deleteCustomerByEmail( customerEmailAddress );
|
||||
await merchant.login();
|
||||
|
||||
// Set checkbox in the settings to enable registration in my account
|
||||
await merchant.openSettings('account');
|
||||
await setCheckbox('#woocommerce_enable_myaccount_registration');
|
||||
await settingsPageSaveChanges();
|
||||
|
||||
|
||||
await merchant.logout();
|
||||
});
|
||||
|
||||
it('can create a new account via my account', async () => {
|
||||
await shopper.gotoMyAccount();
|
||||
await page.waitForSelector('.woocommerce-form-register');
|
||||
await expect(page).toFill('input#reg_email', 'john.doe.test@example.com');
|
||||
await expect(page).toFill('input#reg_email', customerEmailAddress);
|
||||
await expect(page).toClick('button[name="register"]');
|
||||
await page.waitForNavigation({waitUntil: 'networkidle0'});
|
||||
await expect(page).toMatchElement('h1', 'My account');
|
||||
|
@ -33,7 +37,7 @@ const runMyAccountCreateAccountTest = () => {
|
|||
// Verify user has been created successfully
|
||||
await merchant.login();
|
||||
await merchant.openAllUsersView();
|
||||
await expect(page).toMatchElement('td.email.column-email > a', {text: 'john.doe.test@example.com'});
|
||||
await expect(page).toMatchElement('td.email.column-email > a', {text: customerEmailAddress});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/* eslint-disable jest/no-export, jest/no-disabled-tests, jest/expect-expect */
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
const {
|
||||
shopper,
|
||||
merchant,
|
||||
createSimpleProduct,
|
||||
uiUnblocked,
|
||||
deleteAllEmailLogs,
|
||||
} = require( '@woocommerce/e2e-utils' );
|
||||
|
||||
let simplePostIdValue;
|
||||
let orderId;
|
||||
const config = require( 'config' );
|
||||
const simpleProductName = config.get( 'products.simple.name' );
|
||||
const customerEmail = config.get( 'addresses.customer.billing.email' );
|
||||
const storeName = 'WooCommerce Core E2E Test Suite';
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
const {
|
||||
it,
|
||||
describe,
|
||||
beforeAll,
|
||||
} = require( '@jest/globals' );
|
||||
|
||||
const runOrderEmailReceivingTest = () => {
|
||||
describe('Shopper Order Email Receiving', () => {
|
||||
beforeAll(async () => {
|
||||
await merchant.login();
|
||||
await deleteAllEmailLogs();
|
||||
simplePostIdValue = await createSimpleProduct();
|
||||
await merchant.logout();
|
||||
});
|
||||
|
||||
it('should receive order email after purchasing an item', async () => {
|
||||
await shopper.login();
|
||||
|
||||
// Go to the shop and purchase an item
|
||||
await shopper.goToProduct(simplePostIdValue);
|
||||
await shopper.addToCart(simpleProductName);
|
||||
await shopper.goToCheckout();
|
||||
await uiUnblocked();
|
||||
await shopper.placeOrder();
|
||||
// Get order ID from the order received html element on the page
|
||||
orderId = await page.$$eval(".woocommerce-order-overview__order strong", elements => elements.map(item => item.textContent));
|
||||
|
||||
// Verify the new order email has been received
|
||||
await merchant.login();
|
||||
await merchant.openEmailLog();
|
||||
await expect( page ).toMatchElement( '.column-receiver', { text: customerEmail } );
|
||||
await expect( page ).toMatchElement( '.column-subject', { text: `[${storeName}]: New order #${orderId}` } );
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = runOrderEmailReceivingTest;
|
|
@ -4,7 +4,12 @@ echo "Initializing WooCommerce E2E"
|
|||
|
||||
wp plugin activate woocommerce
|
||||
wp theme install twentynineteen --activate
|
||||
wp user create customer customer@woocommercecoree2etestsuite.com --user_pass=password --role=subscriber --path=/var/www/html
|
||||
wp user create customer customer@woocommercecoree2etestsuite.com \
|
||||
--user_pass=password \
|
||||
--role=subscriber \
|
||||
--first_name='Jane' \
|
||||
--last_name='Smith' \
|
||||
--path=/var/www/html
|
||||
|
||||
# we cannot create API keys for the API, so we using basic auth, this plugin allows that.
|
||||
wp plugin install https://github.com/WP-API/Basic-Auth/archive/master.zip --activate
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
# Unreleased
|
||||
|
||||
## Added
|
||||
|
||||
- `takeScreenshotFor` utility function to take screenshots within tests
|
||||
- `sendFailedTestScreenshotToSlack` to send the screenshot to the configured Slack channel
|
||||
- `sendFailedTestMessageToSlack` to send the context for screenshot to the configured Slack channel
|
||||
- `toBeInRange` expect numeric range matcher
|
||||
|
||||
# 0.2.1
|
||||
|
||||
|
|
|
@ -79,6 +79,12 @@ The test sequencer provides a screenshot function for test failures. To enable s
|
|||
WC_E2E_SCREENSHOTS=1 npx wc-e2e test:e2e
|
||||
```
|
||||
|
||||
To take adhoc in test screenshots use
|
||||
|
||||
```js
|
||||
await takeScreenshotFor( 'name of current step' );
|
||||
```
|
||||
|
||||
Screenshots will be saved to `tests/e2e/screenshots`. This folder is cleared at the beginning of each test run.
|
||||
|
||||
### Jest Puppeteer Config
|
||||
|
@ -147,6 +153,7 @@ To implement the Slackbot in your CI:
|
|||
- `files:write`
|
||||
- `incoming-webhook`
|
||||
- Add the app to your channel
|
||||
- Invite the Slack app user to your channel `/invite @your-slackbot-user`
|
||||
- In your CI environment
|
||||
- Add the environment variable `WC_E2E_SCREENSHOTS=1`
|
||||
- Add your app Oauth token to a CI secret `E2E_SLACK_TOKEN`
|
||||
|
|
|
@ -43,7 +43,10 @@ if ( appPath ) {
|
|||
if ( ! fs.existsSync( customInitFile ) ) {
|
||||
customInitFile = '';
|
||||
}
|
||||
} else {
|
||||
customInitFile = '';
|
||||
}
|
||||
|
||||
const appInitFile = customInitFile ? customInitFile : path.resolve( appPath, 'tests/e2e/docker/initialize.sh' );
|
||||
// If found, copy it into the wp-cli Docker context so
|
||||
// it gets picked up by the entrypoint script.
|
||||
|
|
|
@ -5,6 +5,7 @@ const babelConfig = require( './babel.config' );
|
|||
const esLintConfig = require( './.eslintrc.js' );
|
||||
const allE2EConfig = require( './config' );
|
||||
const allE2EUtils = require( './utils' );
|
||||
const slackUtils = require( './src/slack' );
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
|
@ -16,4 +17,5 @@ module.exports = {
|
|||
...allE2EConfig,
|
||||
...allE2EUtils,
|
||||
...allPuppeteerUtils,
|
||||
...slackUtils,
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@woocommerce/e2e-environment",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.1",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
|
|
@ -4,10 +4,8 @@ import {
|
|||
sendFailedTestMessageToSlack,
|
||||
} from '../slack';
|
||||
|
||||
const path = require( 'path' );
|
||||
const mkdirp = require( 'mkdirp' );
|
||||
import { bind } from 'jest-each';
|
||||
const { getAppRoot } = require( '../../utils' );
|
||||
const { takeScreenshotFor } = require( '../../utils' );
|
||||
|
||||
/**
|
||||
* Override the test case method so we can take screenshots of assertion failures.
|
||||
|
@ -88,22 +86,11 @@ const screenshotTest = async ( testName, callback ) => {
|
|||
try {
|
||||
await callback();
|
||||
} catch ( e ) {
|
||||
const testTitle = testName.replace( /\.$/, '' );
|
||||
const appPath = getAppRoot();
|
||||
const savePath = path.resolve( appPath, 'tests/e2e/screenshots' );
|
||||
const filePath = path.join(
|
||||
savePath,
|
||||
`${ testTitle }.png`.replace( /[^a-z0-9.-]+/gi, '-' )
|
||||
);
|
||||
|
||||
mkdirp.sync( savePath );
|
||||
await page.screenshot( {
|
||||
path: filePath,
|
||||
fullPage: true,
|
||||
} );
|
||||
|
||||
await sendFailedTestMessageToSlack( testTitle );
|
||||
await sendFailedTestScreenshotToSlack( filePath );
|
||||
const { title, filePath } = await takeScreenshotFor( testName );
|
||||
await sendFailedTestMessageToSlack( title );
|
||||
if ( filePath ) {
|
||||
await sendFailedTestScreenshotToSlack( filePath );
|
||||
}
|
||||
|
||||
throw ( e );
|
||||
}
|
||||
|
|
|
@ -51,37 +51,6 @@ async function setupBrowser() {
|
|||
await setBrowserViewport( 'large' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates to woocommerce import page and imports sample products.
|
||||
*
|
||||
* @return {Promise} Promise resolving once products have been imported.
|
||||
*/
|
||||
async function importSampleProducts() {
|
||||
await switchUserToAdmin();
|
||||
// Visit Import Products page.
|
||||
await visitAdminPage(
|
||||
'edit.php',
|
||||
'post_type=product&page=product_importer'
|
||||
);
|
||||
await page.click( 'a.woocommerce-importer-toggle-advanced-options' );
|
||||
await page.focus( '#woocommerce-importer-file-url' );
|
||||
// local path for sample data that is included with woo.
|
||||
await page.keyboard.type(
|
||||
'wp-content/plugins/woocommerce/sample-data/sample_products.csv'
|
||||
);
|
||||
await page.click( '.wc-actions .button-next' );
|
||||
await page.waitForSelector( '.wc-importer-mapping-table' );
|
||||
await page.select(
|
||||
'.wc-importer-mapping-table tr:nth-child(29) select',
|
||||
''
|
||||
);
|
||||
await page.click( '.wc-actions .button-next' );
|
||||
await page.waitForXPath(
|
||||
"//*[@class='woocommerce-importer-done' and contains(., 'Import complete! ')]"
|
||||
);
|
||||
await switchUserToTest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an event listener to the page to handle additions of page event
|
||||
* handlers, to assure that they are removed at test teardown.
|
||||
|
@ -101,6 +70,23 @@ function removePageEvents() {
|
|||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an expect range matcher.
|
||||
* @see https://jestjs.io/docs/expect#expectextendmatchers
|
||||
*/
|
||||
expect.extend({
|
||||
toBeInRange: function (received, floor, ceiling) {
|
||||
const pass = received >= floor && received <= ceiling;
|
||||
const condition = pass ? 'not to be' : 'to be';
|
||||
|
||||
return {
|
||||
message: () =>
|
||||
`expected ${received} ${condition} within range ${floor} - ${ceiling}`,
|
||||
pass,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Adds a page event handler to emit uncaught exception to process if one of
|
||||
* the observed console logging types is encountered.
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
export * from './reporter';
|
||||
const slackUtils = require( './reporter' );
|
||||
|
||||
module.exports = slackUtils;
|
||||
|
|
|
@ -69,7 +69,7 @@ const initializeSlack = () => {
|
|||
* @param testName
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function sendFailedTestMessageToSlack( testName ) {
|
||||
async function sendFailedTestMessageToSlack( testName ) {
|
||||
const { branch, commit, webUrl } = initializeSlack();
|
||||
if ( ! branch ) {
|
||||
return;
|
||||
|
@ -127,7 +127,7 @@ export async function sendFailedTestMessageToSlack( testName ) {
|
|||
* @param screenshotOfFailedTest
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function sendFailedTestScreenshotToSlack( screenshotOfFailedTest ) {
|
||||
async function sendFailedTestScreenshotToSlack( screenshotOfFailedTest ) {
|
||||
const pr = initializeSlack();
|
||||
if ( ! pr ) {
|
||||
return;
|
||||
|
@ -154,3 +154,8 @@ export async function sendFailedTestScreenshotToSlack( screenshotOfFailedTest )
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sendFailedTestMessageToSlack,
|
||||
sendFailedTestScreenshotToSlack,
|
||||
};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
const getAppRoot = require( './app-root' );
|
||||
const { getAppName, getAppBase } = require( './app-name' );
|
||||
const { getTestConfig, getAdminConfig } = require( './test-config' );
|
||||
const takeScreenshotFor = require( './take-screenshot' );
|
||||
|
||||
module.exports = {
|
||||
getAppBase,
|
||||
|
@ -8,4 +9,5 @@ module.exports = {
|
|||
getAppName,
|
||||
getTestConfig,
|
||||
getAdminConfig,
|
||||
takeScreenshotFor,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
const path = require( 'path' );
|
||||
const mkdirp = require( 'mkdirp' );
|
||||
const getAppRoot = require( './app-root' );
|
||||
|
||||
/**
|
||||
* Take a screenshot if browser context exists.
|
||||
* @param message
|
||||
* @returns {Promise<{filePath: string, title: string}|{filePath: *, title: *}>}
|
||||
*/
|
||||
const takeScreenshotFor = async ( message ) => {
|
||||
const title = message.replace( /\.$/, '' );
|
||||
const appPath = getAppRoot();
|
||||
const savePath = path.resolve( appPath, 'tests/e2e/screenshots' );
|
||||
const filePath = path.join(
|
||||
savePath,
|
||||
`${ title }.png`.replace( /[^a-z0-9.-]+/gi, '-' )
|
||||
);
|
||||
|
||||
mkdirp.sync( savePath );
|
||||
try {
|
||||
await page.screenshot({
|
||||
path: filePath,
|
||||
fullPage: true,
|
||||
});
|
||||
} catch ( error ) {
|
||||
return {
|
||||
title,
|
||||
filePath: '',
|
||||
};
|
||||
}
|
||||
return {
|
||||
title,
|
||||
filePath,
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = takeScreenshotFor;
|
|
@ -0,0 +1,6 @@
|
|||
/*
|
||||
* Internal dependencies
|
||||
*/
|
||||
const { runCartCalculateShippingTest } = require( '@woocommerce/e2e-core-tests' );
|
||||
|
||||
runCartCalculateShippingTest();
|
|
@ -0,0 +1,6 @@
|
|||
/*
|
||||
* Internal dependencies
|
||||
*/
|
||||
const { runCartRedirectionTest } = require( '@woocommerce/e2e-core-tests' );
|
||||
|
||||
runCartRedirectionTest();
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue