Merge branch 'trunk' into e2e/e2e-merchant-add-simple-product
This commit is contained in:
commit
5d4597c31c
|
@ -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');
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
"pelago/emogrifier": "3.1.0",
|
||||
"psr/container": "1.0.0",
|
||||
"woocommerce/action-scheduler": "3.1.6",
|
||||
"woocommerce/woocommerce-admin": "2.2.1",
|
||||
"woocommerce/woocommerce-admin": "2.2.2-rc.1",
|
||||
"woocommerce/woocommerce-blocks": "4.9.1"
|
||||
},
|
||||
"require-dev": {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "11158f53934e897bd84be190a184ec87",
|
||||
"content-hash": "be33d948ed1d2ee3a7a23ef657f3148d",
|
||||
"packages": [
|
||||
{
|
||||
"name": "automattic/jetpack-autoloader",
|
||||
|
@ -86,16 +86,16 @@
|
|||
},
|
||||
{
|
||||
"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": {
|
||||
|
@ -189,6 +189,7 @@
|
|||
"majima",
|
||||
"mako",
|
||||
"mediawiki",
|
||||
"miaoxing",
|
||||
"modulework",
|
||||
"modx",
|
||||
"moodle",
|
||||
|
@ -206,6 +207,7 @@
|
|||
"sydes",
|
||||
"sylius",
|
||||
"symfony",
|
||||
"tastyigniter",
|
||||
"typo3",
|
||||
"wordpress",
|
||||
"yawik",
|
||||
|
@ -226,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",
|
||||
|
@ -501,16 +503,16 @@
|
|||
},
|
||||
{
|
||||
"name": "woocommerce/woocommerce-admin",
|
||||
"version": "2.2.1",
|
||||
"version": "2.2.2-rc.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/woocommerce/woocommerce-admin.git",
|
||||
"reference": "78cc9c5ef7de5be5bd0f9208483e5ae97422be9a"
|
||||
"reference": "0d305d1716481a0cc2010ec7b0a608a2d3c8ebe4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/78cc9c5ef7de5be5bd0f9208483e5ae97422be9a",
|
||||
"reference": "78cc9c5ef7de5be5bd0f9208483e5ae97422be9a",
|
||||
"url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/0d305d1716481a0cc2010ec7b0a608a2d3c8ebe4",
|
||||
"reference": "0d305d1716481a0cc2010ec7b0a608a2d3c8ebe4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -542,7 +544,7 @@
|
|||
],
|
||||
"description": "A modern, javascript-driven WooCommerce Admin experience.",
|
||||
"homepage": "https://github.com/woocommerce/woocommerce-admin",
|
||||
"time": "2021-04-02T19:30:03+00:00"
|
||||
"time": "2021-04-28T19:39:33+00:00"
|
||||
},
|
||||
{
|
||||
"name": "woocommerce/woocommerce-blocks",
|
||||
|
@ -589,10 +591,6 @@
|
|||
"gutenberg",
|
||||
"woocommerce"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues",
|
||||
"source": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/tree/v4.9.1"
|
||||
},
|
||||
"time": "2021-04-13T16:11:16+00:00"
|
||||
}
|
||||
],
|
||||
|
|
|
@ -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' );
|
||||
|
||||
|
|
|
@ -46,12 +46,19 @@ class WC_Settings_Emails extends WC_Settings_Page {
|
|||
* @return array
|
||||
*/
|
||||
public function get_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',
|
||||
),
|
||||
|
|
|
@ -229,7 +229,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,
|
||||
|
|
|
@ -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' ),
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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' ) ) {
|
||||
|
@ -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 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -9,11 +9,12 @@ 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
|
||||
|
@ -35,6 +36,7 @@ final class Container implements \Psr\Container\ContainerInterface {
|
|||
*/
|
||||
private $service_providers = array(
|
||||
ProxiesServiceProvider::class,
|
||||
ThemeManagementServiceProvider::class,
|
||||
DownloadPermissionsAdjusterServiceProvider::class,
|
||||
AssignDefaultCategoryServiceProvider::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;
|
||||
}
|
||||
}
|
||||
|
|
@ -41,7 +41,6 @@ const runOnboardingFlowTest = () => {
|
|||
}
|
||||
|
||||
it('can start and complete onboarding when visiting the site for the first time.', async () => {
|
||||
await merchant.runSetupWizard();
|
||||
await completeOnboardingWizard();
|
||||
});
|
||||
});
|
||||
|
@ -62,7 +61,6 @@ const runTaskListTest = () => {
|
|||
// 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();
|
||||
await page.waitForNavigation({waitUntil: 'networkidle0'});
|
||||
|
||||
// Wait for "Proceed" button to become active
|
||||
await page.waitForSelector('button.is-primary:not(:disabled)');
|
||||
|
|
|
@ -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'),
|
||||
|
@ -43,7 +43,7 @@ const runOrderApplyCouponTest = () => {
|
|||
|
||||
it('can apply a coupon', async () => {
|
||||
const couponDialog = await expect(page).toDisplayDialog(async () => {
|
||||
await expect(page).toClick('button.add-coupon');
|
||||
await evalAndClick('button.add-coupon');
|
||||
});
|
||||
|
||||
expect(couponDialog.message()).toMatch(couponDialogMessage);
|
||||
|
|
|
@ -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);
|
||||
})
|
||||
});
|
||||
};
|
||||
|
|
|
@ -61,6 +61,8 @@ const runCartCalculateShippingTest = () => {
|
|||
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 () => {
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
uiUnblocked,
|
||||
setCheckbox,
|
||||
settingsPageSaveChanges,
|
||||
addShippingZoneAndMethod,
|
||||
withRestApi,
|
||||
} = require( '@woocommerce/e2e-utils' );
|
||||
|
||||
|
@ -36,6 +37,10 @@ const runCheckoutCreateAccountTest = () => {
|
|||
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
## Added
|
||||
|
||||
- `emptyCart()` Shopper flow helper that empties the cart
|
||||
- `deleteAllShippingZones` Delete all the existing shipping zones
|
||||
- `deleteAllShippingZones()` Delete all the existing shipping zones
|
||||
- constants
|
||||
- `WP_ADMIN_POST_TYPE`
|
||||
- `WP_ADMIN_NEW_POST_TYPE`
|
||||
|
@ -11,6 +11,7 @@
|
|||
- `WP_ADMIN_WC_HOME`
|
||||
- `IS_RETEST_MODE`
|
||||
- `withRestApi` flow containing utility functions that manage data with the rest api
|
||||
- `waitForSelectorWithoutThrow` - conditionally wait for a selector without throwing an error
|
||||
|
||||
# 0.1.4
|
||||
|
||||
|
|
|
@ -181,6 +181,7 @@ This package provides support for enabling retries in tests:
|
|||
| `selectOrderAction` | `action` | Helper method to select an order action in the `Order Actions` postbox |
|
||||
| `clickUpdateOrder` | `noticeText`, `waitForSave` | Helper method to click the Update button on the order details page |
|
||||
| `deleteAllShippingZones` | | Delete all the existing shipping zones |
|
||||
| `waitForSelectorWithoutThrow` | `selector`, `timeoutInSeconds` | conditionally wait for a selector without throwing an error. Default timeout is 5 seconds |
|
||||
|
||||
### Test Utilities
|
||||
|
||||
|
|
|
@ -14,7 +14,8 @@ import {
|
|||
setCheckbox,
|
||||
unsetCheckbox,
|
||||
evalAndClick,
|
||||
clearAndFillInput,
|
||||
backboneUnblocked,
|
||||
waitForSelectorWithoutThrow,
|
||||
} from './page-utils';
|
||||
import factories from './factories';
|
||||
|
||||
|
@ -58,6 +59,7 @@ const waitAndClickPrimary = async ( waitForNetworkIdle = true ) => {
|
|||
*/
|
||||
const completeOnboardingWizard = async () => {
|
||||
// Store Details section
|
||||
await merchant.runSetupWizard();
|
||||
|
||||
// Fill store's address - first line
|
||||
await expect( page ).toFill( '#inspector-text-control-0', config.get( 'addresses.admin.store.addressfirstline' ) );
|
||||
|
@ -84,8 +86,8 @@ const completeOnboardingWizard = async () => {
|
|||
await page.click( 'button.is-primary', { text: 'Continue' } );
|
||||
|
||||
// Wait for usage tracking pop-up window to appear on a new site
|
||||
if ( ! IS_RETEST_MODE ) {
|
||||
await page.waitForSelector('.components-modal__header-heading');
|
||||
const usageTrackingHeader = await page.$('.components-modal__header-heading');
|
||||
if ( usageTrackingHeader ) {
|
||||
await expect(page).toMatchElement(
|
||||
'.components-modal__header-heading', {text: 'Build a better WooCommerce'}
|
||||
);
|
||||
|
@ -163,24 +165,18 @@ const completeOnboardingWizard = async () => {
|
|||
}
|
||||
|
||||
// Wait for homescreen welcome modal to appear
|
||||
await page.waitForSelector( '.woocommerce__welcome-modal__page-content__header' );
|
||||
await expect( page ).toMatchElement(
|
||||
'.woocommerce__welcome-modal__page-content__header', { text: 'Welcome to your WooCommerce store\’s online HQ!' }
|
||||
);
|
||||
|
||||
// Wait for "Next" button to become active
|
||||
await page.waitForSelector( 'button.components-guide__forward-button' );
|
||||
// Click on "Next" button to move to the next step
|
||||
await page.click( 'button.components-guide__forward-button' );
|
||||
|
||||
// Wait for "Next" button to become active
|
||||
await page.waitForSelector( 'button.components-guide__forward-button' );
|
||||
// Click on "Next" button to move to the next step
|
||||
await page.click( 'button.components-guide__forward-button' );
|
||||
let welcomeHeader = await waitForSelectorWithoutThrow( '.woocommerce__welcome-modal__page-content' );
|
||||
if ( ! welcomeHeader ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Click two Next buttons
|
||||
for ( let b = 0; b < 2; b++ ) {
|
||||
await page.waitForSelector('button.components-guide__forward-button');
|
||||
await page.click('button.components-guide__forward-button');
|
||||
}
|
||||
// Wait for "Let's go" button to become active
|
||||
await page.waitForSelector( 'button.components-guide__finish-button' );
|
||||
// Click on "Let's go" button to move to the next step
|
||||
await page.click( 'button.components-guide__finish-button' );
|
||||
};
|
||||
|
||||
|
@ -437,7 +433,7 @@ const addProductToOrder = async ( orderId, productName ) => {
|
|||
await expect( page ).toClick( 'li[aria-selected="true"]' );
|
||||
await page.click( '.wc-backbone-modal-content #btn-ok' );
|
||||
|
||||
await uiUnblocked();
|
||||
await backboneUnblocked();
|
||||
|
||||
// Verify the product we added shows as a line item now
|
||||
await expect( page ).toMatchElement( '.wc-order-item-name', { text: productName } );
|
||||
|
|
|
@ -82,6 +82,32 @@ export const uiUnblocked = async () => {
|
|||
await page.waitForFunction( () => ! Boolean( document.querySelector( '.blockUI' ) ) );
|
||||
};
|
||||
|
||||
/**
|
||||
* Wait for backbone blocking to end.
|
||||
*/
|
||||
export const backboneUnblocked = async () => {
|
||||
await page.waitForFunction( () => ! Boolean( document.querySelector( '.wc-backbone-modal' ) ) );
|
||||
};
|
||||
|
||||
/**
|
||||
* Conditionally wait for a selector without throwing an error.
|
||||
*
|
||||
* @param selector
|
||||
* @param timeoutInSeconds
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
export const waitForSelectorWithoutThrow = async ( selector, timeoutInSeconds = 5 ) => {
|
||||
let selected = await page.$( selector );
|
||||
for ( let s = 0; s < timeoutInSeconds; s++ ) {
|
||||
if ( selected ) {
|
||||
break;
|
||||
}
|
||||
await page.waitFor( 1000 );
|
||||
selected = await page.$( selector );
|
||||
}
|
||||
return Boolean( selected );
|
||||
};
|
||||
|
||||
/**
|
||||
* Publish, verify that item was published. Trash, verify that item was trashed.
|
||||
*
|
||||
|
@ -236,6 +262,10 @@ export const searchForOrder = async (value, orderId, customerName) => {
|
|||
*/
|
||||
export const applyCoupon = async ( couponCode ) => {
|
||||
try {
|
||||
await Promise.all([
|
||||
page.reload(),
|
||||
page.waitForNavigation( { waitUntil: 'networkidle0' } ),
|
||||
]);
|
||||
await expect(page).toClick('a', {text: 'Click here to enter your code'});
|
||||
await uiUnblocked();
|
||||
await clearAndFillInput('#coupon_code', couponCode);
|
||||
|
@ -255,6 +285,10 @@ export const applyCoupon = async ( couponCode ) => {
|
|||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const removeCoupon = async ( couponCode ) => {
|
||||
await Promise.all([
|
||||
page.reload(),
|
||||
page.waitForNavigation( { waitUntil: 'networkidle0' } ),
|
||||
]);
|
||||
await expect(page).toClick('[data-coupon="'+couponCode.toLowerCase()+'"]', {text: '[Remove]'});
|
||||
await uiUnblocked();
|
||||
await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon has been removed.'});
|
||||
|
|
|
@ -103,7 +103,14 @@ class WC_Unit_Tests_Bootstrap {
|
|||
* @throws Exception Error when initializing one of the hacks.
|
||||
*/
|
||||
private function initialize_code_hacker() {
|
||||
CodeHacker::initialize( array( __DIR__ . '/../../includes/' ) );
|
||||
$wp_dir = getenv( 'WP_TESTS_WP_DIR' ) ? getenv( 'WP_TESTS_WP_DIR' ) : sys_get_temp_dir() . '/wordpress';
|
||||
CodeHacker::initialize(
|
||||
array(
|
||||
$this->plugin_dir . '/includes/',
|
||||
$wp_dir . '/wp-includes/class-wp-customize-manager.php',
|
||||
)
|
||||
);
|
||||
|
||||
$replaceable_functions = include_once __DIR__ . '/mockable-functions.php';
|
||||
if ( ! empty( $replaceable_functions ) ) {
|
||||
FunctionsMockerHack::initialize( $replaceable_functions );
|
||||
|
|
|
@ -593,32 +593,32 @@ class WC_Tests_Formatting_Functions extends WC_Unit_Test_Case {
|
|||
*/
|
||||
public function test_wc_price() {
|
||||
// Common prices.
|
||||
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">£</span>1.00</bdi></span>', wc_price( 1 ) );
|
||||
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">£</span>1.10</bdi></span>', wc_price( 1.1 ) );
|
||||
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">£</span>1.17</bdi></span>', wc_price( 1.17 ) );
|
||||
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">£</span>1,111.17</bdi></span>', wc_price( 1111.17 ) );
|
||||
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">£</span>0.00</bdi></span>', wc_price( 0 ) );
|
||||
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">$</span>1.00</bdi></span>', wc_price( 1 ) );
|
||||
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">$</span>1.10</bdi></span>', wc_price( 1.1 ) );
|
||||
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">$</span>1.17</bdi></span>', wc_price( 1.17 ) );
|
||||
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">$</span>1,111.17</bdi></span>', wc_price( 1111.17 ) );
|
||||
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">$</span>0.00</bdi></span>', wc_price( 0 ) );
|
||||
|
||||
// Different currency.
|
||||
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">$</span>1,111.17</bdi></span>', wc_price( 1111.17, array( 'currency' => 'USD' ) ) );
|
||||
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">£</span>1,111.17</bdi></span>', wc_price( 1111.17, array( 'currency' => 'GBP' ) ) );
|
||||
|
||||
// Negative price.
|
||||
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi>-<span class="woocommerce-Price-currencySymbol">£</span>1.17</bdi></span>', wc_price( -1.17 ) );
|
||||
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi>-<span class="woocommerce-Price-currencySymbol">$</span>1.17</bdi></span>', wc_price( -1.17 ) );
|
||||
|
||||
// Bogus prices.
|
||||
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">£</span>0.00</bdi></span>', wc_price( null ) );
|
||||
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">£</span>0.00</bdi></span>', wc_price( 'Q' ) );
|
||||
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">£</span>0.00</bdi></span>', wc_price( 'ಠ_ಠ' ) );
|
||||
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">$</span>0.00</bdi></span>', wc_price( null ) );
|
||||
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">$</span>0.00</bdi></span>', wc_price( 'Q' ) );
|
||||
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">$</span>0.00</bdi></span>', wc_price( 'ಠ_ಠ' ) );
|
||||
|
||||
// Trim zeros.
|
||||
add_filter( 'woocommerce_price_trim_zeros', '__return_true' );
|
||||
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">£</span>1</bdi></span>', wc_price( 1.00 ) );
|
||||
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">$</span>1</bdi></span>', wc_price( 1.00 ) );
|
||||
remove_filter( 'woocommerce_price_trim_zeros', '__return_true' );
|
||||
|
||||
// Ex tax label.
|
||||
$calc_taxes = get_option( 'woocommerce_calc_taxes' );
|
||||
update_option( 'woocommerce_calc_taxes', 'yes' );
|
||||
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">£</span>1,111.17</bdi></span> <small class="woocommerce-Price-taxLabel tax_label">(ex. tax)</small>', wc_price( '1111.17', array( 'ex_tax_label' => true ) ) );
|
||||
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">$</span>1,111.17</bdi></span> <small class="woocommerce-Price-taxLabel tax_label">(ex. tax)</small>', wc_price( '1111.17', array( 'ex_tax_label' => true ) ) );
|
||||
update_option( 'woocommerce_calc_taxes', $calc_taxes );
|
||||
}
|
||||
|
||||
|
@ -926,7 +926,7 @@ class WC_Tests_Formatting_Functions extends WC_Unit_Test_Case {
|
|||
* @since 3.3.0
|
||||
*/
|
||||
public function test_wc_format_sale_price() {
|
||||
$this->assertEquals( '<del aria-hidden="true"><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">£</span>10.00</bdi></span></del> <ins><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">£</span>5.00</bdi></span></ins>', wc_format_sale_price( '10', '5' ) );
|
||||
$this->assertEquals( '<del aria-hidden="true"><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">$</span>10.00</bdi></span></del> <ins><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">$</span>5.00</bdi></span></ins>', wc_format_sale_price( '10', '5' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -935,7 +935,7 @@ class WC_Tests_Formatting_Functions extends WC_Unit_Test_Case {
|
|||
* @since 3.3.0
|
||||
*/
|
||||
public function test_wc_format_price_range() {
|
||||
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">£</span>10.00</bdi></span> – <span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">£</span>5.00</bdi></span>', wc_format_price_range( '10', '5' ) );
|
||||
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">$</span>10.00</bdi></span> – <span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">$</span>5.00</bdi></span>', wc_format_price_range( '10', '5' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -259,15 +259,15 @@ class WC_Tests_Product_Data extends WC_Unit_Test_Case {
|
|||
|
||||
$product = wc_get_product( $product1_id );
|
||||
$this->assertEquals( $product1_id, $product->get_id() );
|
||||
$this->assertEquals( '<del aria-hidden="true"><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">£</span>10.00</bdi></span></del> <ins><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">£</span>7.00</bdi></span></ins>', $product->get_price_html() );
|
||||
$this->assertEquals( '<del aria-hidden="true"><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">$</span>10.00</bdi></span></del> <ins><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">$</span>7.00</bdi></span></ins>', $product->get_price_html() );
|
||||
|
||||
$product = wc_get_product( $product2_id );
|
||||
$this->assertEquals( $product2_id, $product->get_id() );
|
||||
$this->assertEquals( '<del aria-hidden="true"><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">£</span>20.00</bdi></span></del> <ins><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">£</span>16.00</bdi></span></ins>', $product->get_price_html() );
|
||||
$this->assertEquals( '<del aria-hidden="true"><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">$</span>20.00</bdi></span></del> <ins><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">$</span>16.00</bdi></span></ins>', $product->get_price_html() );
|
||||
|
||||
$product = wc_get_product( $product3_id );
|
||||
$this->assertEquals( $product3_id, $product->get_id() );
|
||||
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">£</span>50.00</bdi></span>', $product->get_price_html() );
|
||||
$this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol">$</span>50.00</bdi></span>', $product->get_price_html() );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -30,7 +30,7 @@ class WC_Tests_Core_Functions extends WC_Unit_Test_Case {
|
|||
*/
|
||||
public function test_get_woocommerce_currency() {
|
||||
|
||||
$this->assertEquals( 'GBP', get_woocommerce_currency() );
|
||||
$this->assertEquals( 'USD', get_woocommerce_currency() );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -222,10 +222,10 @@ class WC_Tests_Core_Functions extends WC_Unit_Test_Case {
|
|||
public function test_get_woocommerce_currency_symbol() {
|
||||
|
||||
// Default currency.
|
||||
$this->assertEquals( '£', get_woocommerce_currency_symbol() );
|
||||
$this->assertEquals( '$', get_woocommerce_currency_symbol() );
|
||||
|
||||
// Given specific currency.
|
||||
$this->assertEquals( '$', get_woocommerce_currency_symbol( 'USD' ) );
|
||||
$this->assertEquals( '£', get_woocommerce_currency_symbol( 'GBP' ) );
|
||||
|
||||
// Each case.
|
||||
foreach ( array_keys( get_woocommerce_currencies() ) as $currency_code ) {
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
/**
|
||||
* Tests for WC_Shop_Customizer
|
||||
*/
|
||||
|
||||
use Automattic\WooCommerce\Internal\ThemeSupport;
|
||||
|
||||
require_once ABSPATH . WPINC . '/class-wp-customize-manager.php';
|
||||
require_once ABSPATH . WPINC . '/class-wp-customize-control.php';
|
||||
|
||||
/**
|
||||
* Tests for WC_Shop_Customizer
|
||||
*/
|
||||
class WC_Shop_Customizer_Test extends WC_Unit_Test_Case {
|
||||
|
||||
/**
|
||||
* Runs before each test of the class.
|
||||
*/
|
||||
public function setUp() {
|
||||
remove_theme_support( 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox add_sections should add controls for image size settings only if these sizes haven't been explicitly declared as theme support (not counting default declarations).
|
||||
*
|
||||
* @testWith ["single_image_width", true, false]
|
||||
* ["single_image_width", false, true]
|
||||
* ["thumbnail_image_width", true, false]
|
||||
* ["thumbnail_image_width", false, true]
|
||||
*
|
||||
* @param string $option_name The option name to test, either 'single_image_width' or 'thumbnail_image_width'.
|
||||
* @param bool $add_explicit_theme_support True to test when theme support is added explicitly (not as a default value).
|
||||
* @param bool $expected_to_have_added_customization True to expect the customization to have been added, false to expect it no to have been added.
|
||||
*/
|
||||
public function test_add_sections_should_add_image_controls_only_if_no_theme_support_for_image_sizes_is_defined( $option_name, $add_explicit_theme_support, $expected_to_have_added_customization ) {
|
||||
$customize_manager = $this->createMock( WP_Customize_Manager::class );
|
||||
$added_settings = array();
|
||||
$added_controls = array();
|
||||
|
||||
$add_setting_callback = function( $id, $args = array() ) use ( &$added_settings ) {
|
||||
array_push( $added_settings, $id );
|
||||
};
|
||||
$add_control_callback = function( $id, $args = array() ) use ( &$added_controls ) {
|
||||
array_push( $added_controls, $id );
|
||||
};
|
||||
$customize_manager->method( 'add_setting' )->will( $this->returnCallback( $add_setting_callback ) );
|
||||
$customize_manager->method( 'add_control' )->will( $this->returnCallback( $add_control_callback ) );
|
||||
|
||||
$theme_support = $this->get_instance_of( ThemeSupport::class );
|
||||
$add_support_method = $add_explicit_theme_support ? 'add_options' : 'add_default_options';
|
||||
$theme_support->$add_support_method( array( $option_name => 1234 ) );
|
||||
|
||||
$sut = $this->get_legacy_instance_of( WC_Shop_Customizer::class );
|
||||
$sut->add_sections( $customize_manager );
|
||||
|
||||
$this->assertEquals( $expected_to_have_added_customization, in_array( 'woocommerce_' . $option_name, $added_settings, true ) );
|
||||
$this->assertEquals( $expected_to_have_added_customization, in_array( 'woocommerce_' . $option_name, $added_controls, true ) );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,254 @@
|
|||
<?php
|
||||
/**
|
||||
* Tests for ThemeSupport
|
||||
*
|
||||
* @package Automattic\WooCommerce\Tests\ThemeSupport
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Tests\ThemeManagement;
|
||||
|
||||
use Automattic\WooCommerce\Internal\ThemeSupport;
|
||||
|
||||
/**
|
||||
* Tests for ThemeSupport
|
||||
*/
|
||||
class ThemeSupportTest extends \WC_Unit_Test_Case {
|
||||
|
||||
/**
|
||||
* The system under test.
|
||||
*
|
||||
* @var ThemeSupport
|
||||
*/
|
||||
private $sut;
|
||||
|
||||
/**
|
||||
* Runs before each test.
|
||||
*/
|
||||
public function setUp() {
|
||||
$this->sut = $this->get_instance_of( ThemeSupport::class );
|
||||
remove_theme_support( 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox add_options should add the supplied options under the 'woocommerce' feature.
|
||||
*/
|
||||
public function test_add_options() {
|
||||
$actual_added_feature = null;
|
||||
$actual_added_options = null;
|
||||
|
||||
$this->register_legacy_proxy_function_mocks(
|
||||
array(
|
||||
'add_theme_support' => function( $feature, ...$args ) use ( &$actual_added_feature, &$actual_added_options ) {
|
||||
$actual_added_feature = $feature;
|
||||
$actual_added_options = $args;
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
$options = array( 'foo' => 'bar' );
|
||||
$this->sut->add_options( $options );
|
||||
|
||||
$this->assertEquals( 'woocommerce', $actual_added_feature );
|
||||
$this->assertEquals( $options, $actual_added_options[0] );
|
||||
|
||||
$this->reset_legacy_proxy_mocks();
|
||||
|
||||
$this->sut->add_options( $options );
|
||||
|
||||
$actual_retrieved_options = get_theme_support( 'woocommerce' )[0];
|
||||
$this->assertEquals( $options, $actual_retrieved_options );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox add_default_options should add the supplied options under the 'woocommerce' feature on a '_defaults' key.
|
||||
*/
|
||||
public function test_2_add_default_options() {
|
||||
$actual_added_options = array();
|
||||
|
||||
$this->register_legacy_proxy_function_mocks(
|
||||
array(
|
||||
'add_theme_support' => function( $feature, ...$args ) use ( &$actual_added_options ) {
|
||||
array_push( $actual_added_options, $args );
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
$this->sut->add_default_options( array( 'foo' => 'bar' ) );
|
||||
$this->sut->add_default_options( array( 'fizz' => 'buzz' ) );
|
||||
|
||||
$expected_added_options = array(
|
||||
array(
|
||||
array(
|
||||
ThemeSupport::DEFAULTS_KEY =>
|
||||
array(
|
||||
'foo' => 'bar',
|
||||
),
|
||||
),
|
||||
),
|
||||
array(
|
||||
array(
|
||||
ThemeSupport::DEFAULTS_KEY =>
|
||||
array(
|
||||
'fizz' => 'buzz',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$this->assertEquals( $expected_added_options, $actual_added_options );
|
||||
|
||||
$this->reset_legacy_proxy_mocks();
|
||||
|
||||
$this->sut->add_default_options( array( 'foo' => 'bar' ) );
|
||||
$this->sut->add_default_options( array( 'fizz' => 'buzz' ) );
|
||||
|
||||
$actual_retrieved_options = get_theme_support( 'woocommerce' )[0];
|
||||
$expected_retrieved_options = array(
|
||||
ThemeSupport::DEFAULTS_KEY => array(
|
||||
'foo' => 'bar',
|
||||
'fizz' => 'buzz',
|
||||
),
|
||||
);
|
||||
$this->assertEquals( $expected_retrieved_options, $actual_retrieved_options );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox add_default_options should add the supplied options under the 'woocommerce' feature on a '_defaults' key.
|
||||
*/
|
||||
public function test_add_default_options() {
|
||||
$this->sut->add_default_options( array( 'foo' => 'bar' ) );
|
||||
$this->sut->add_default_options( array( 'fizz' => 'buzz' ) );
|
||||
|
||||
$actual = get_theme_support( 'woocommerce' )[0];
|
||||
$expected = array(
|
||||
ThemeSupport::DEFAULTS_KEY => array(
|
||||
'foo' => 'bar',
|
||||
'fizz' => 'buzz',
|
||||
),
|
||||
);
|
||||
$this->assertEquals( $expected, $actual );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox get_option should return all the options under the 'woocommerce' feature when invoked with blank option name.
|
||||
*/
|
||||
public function test_get_option_with_no_option_name() {
|
||||
$options = array( 'foo' => 'bar' );
|
||||
$this->sut->add_options( $options );
|
||||
|
||||
$actual = $this->sut->get_option();
|
||||
$this->assertEquals( $options, $actual );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox get_option should return null if no 'woocommerce' feature exists and no default value is supplied.
|
||||
*/
|
||||
public function test_get_option_with_no_option_name_when_no_options_exist_and_no_default_value_supplied() {
|
||||
$actual = $this->sut->get_option();
|
||||
$this->assertNull( $actual );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox get_option should return the supplied default value if no 'woocommerce' feature exists.
|
||||
*/
|
||||
public function test_get_option_with_no_option_name_when_no_options_exist_and_default_value_supplied() {
|
||||
$actual = $this->sut->get_option( '', 'DEFAULT' );
|
||||
$this->assertEquals( 'DEFAULT', $actual );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox get_theme_support should return the value of the requested option if it exists.
|
||||
*/
|
||||
public function test_get_theme_support_with_option_name() {
|
||||
$options = array( 'foo' => array( 'bar' => 'fizz' ) );
|
||||
$this->sut->add_options( $options );
|
||||
|
||||
$actual = $this->sut->get_option( 'foo::bar' );
|
||||
$this->assertEquals( 'fizz', $actual );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox get_option should return null if the requested option doesn't exist and no default value is supplied.
|
||||
*/
|
||||
public function test_get_option_with_option_name_when_option_does_not_exist_and_no_default_value_supplied() {
|
||||
$options = array( 'foo' => array( 'bar' => 'fizz' ) );
|
||||
$this->sut->add_options( $options );
|
||||
|
||||
$actual = $this->sut->get_option( 'buzz' );
|
||||
$this->assertNull( $actual );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox get_option should return the supplied default value if the requested option doesn't exist.
|
||||
*/
|
||||
public function test_get_option_with_option_name_when_option_does_not_exist_and_default_value_supplied() {
|
||||
$options = array( 'foo' => array( 'bar' => 'fizz' ) );
|
||||
$this->sut->add_options( $options );
|
||||
|
||||
$actual = $this->sut->get_option( 'buzz', 'DEFAULT' );
|
||||
$this->assertEquals( 'DEFAULT', $actual );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox get_option should return the value of the requested option if it has been defined as a default.
|
||||
*/
|
||||
public function test_get_option_with_option_name_and_option_defined_as_default() {
|
||||
$options = array( 'foo' => array( 'bar' => 'fizz' ) );
|
||||
$this->sut->add_default_options( $options );
|
||||
|
||||
$actual = $this->sut->get_option( 'foo::bar' );
|
||||
$this->assertEquals( 'fizz', $actual );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox has_option should return false if no 'woocommerce' feature exists.
|
||||
*
|
||||
* @testWith [true]
|
||||
* [false]
|
||||
*
|
||||
* @param bool $include_defaults Whether to include defaults in the search or not.
|
||||
*/
|
||||
public function test_has_option_when_no_woocommerce_feature_is_defined( $include_defaults ) {
|
||||
$this->assertFalse( $this->sut->has_option( 'foo::bar', $include_defaults ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox has_option should return false if the specified option has not been defined.
|
||||
*
|
||||
* @testWith [true]
|
||||
* [false]
|
||||
*
|
||||
* @param bool $include_defaults Whether to include defaults in the search or not.
|
||||
*/
|
||||
public function test_has_option_when_option_is_not_defined( $include_defaults ) {
|
||||
$this->sut->add_options( array( 'foo' => 'bar' ) );
|
||||
$this->assertFalse( $this->sut->has_option( 'fizz::buzz', $include_defaults ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox has_option should return true if the specified option has been defined.
|
||||
*
|
||||
* @testWith [true]
|
||||
* [false]
|
||||
*
|
||||
* @param bool $include_defaults Whether to include defaults in the search or not.
|
||||
*/
|
||||
public function test_has_option_when_option_is_defined( $include_defaults ) {
|
||||
$this->sut->add_options( array( 'foo' => 'bar' ) );
|
||||
$this->assertTrue( $this->sut->has_option( 'foo', $include_defaults ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox If an option has been defined as a default, has_theme_support should return true if $include_defaults is passed as true, should return false otherwise.
|
||||
*
|
||||
* @testWith [true, true]
|
||||
* [false, false]
|
||||
*
|
||||
* @param bool $include_defaults Whether to include defaults in the search or not.
|
||||
* @param bool $expected_result The expected return value from the tested method.
|
||||
*/
|
||||
public function test_has_option_when_option_is_defined_as_default( $include_defaults, $expected_result ) {
|
||||
$this->sut->add_default_options( array( 'foo' => 'bar' ) );
|
||||
$this->assertEquals( $expected_result, $this->sut->has_option( 'foo', $include_defaults ) );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Tests\Utilities;
|
||||
|
||||
use Automattic\WooCommerce\Utilities\ArrayUtil;
|
||||
|
||||
/**
|
||||
* A collection of tests for the array utility class.
|
||||
*/
|
||||
class ArrayUtilTest extends \WC_Unit_Test_Case {
|
||||
/**
|
||||
* @testdox `get_nested_value` should return null if the requested key doesn't exist and no default value is supplied.
|
||||
*
|
||||
* @testWith ["foo"]
|
||||
* ["foo::bar"]
|
||||
*
|
||||
* @param string $key The key to test.
|
||||
*/
|
||||
public function test_get_nested_value_returns_null_if_non_existing_key_and_default_not_supplied( $key ) {
|
||||
$array = array( 'fizz' => 'buzz' );
|
||||
$actual = ArrayUtil::get_nested_value( $array, $key );
|
||||
|
||||
$this->assertNull( $actual );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox `get_nested_value` should return the supplied default value if the requested key doesn't exist.
|
||||
*
|
||||
* @testWith ["foo"]
|
||||
* ["foo::bar"]
|
||||
*
|
||||
* @param string $key The key to test.
|
||||
*/
|
||||
public function test_get_nested_value_returns_supplied_default_if_non_existing_key( $key ) {
|
||||
$array = array( 'fizz' => 'buzz' );
|
||||
$actual = ArrayUtil::get_nested_value( $array, $key, 'DEFAULT' );
|
||||
|
||||
$this->assertEquals( 'DEFAULT', $actual );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox `get_nested_value` should return the proper value when a simple key is passed.
|
||||
*/
|
||||
public function test_get_nested_value_works_for_simple_keys() {
|
||||
$array = array( 'foo' => 'bar' );
|
||||
$actual = ArrayUtil::get_nested_value( $array, 'foo' );
|
||||
|
||||
$this->assertEquals( 'bar', $actual );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox `get_nested_value` should return the proper value when a nested key is passed.
|
||||
*/
|
||||
public function test_get_nested_value_works_for_nested_keys() {
|
||||
$array = array(
|
||||
'foo' => array(
|
||||
'bar' => array(
|
||||
'fizz' => 'buzz',
|
||||
),
|
||||
),
|
||||
);
|
||||
$actual = ArrayUtil::get_nested_value( $array, 'foo::bar::fizz' );
|
||||
|
||||
$this->assertEquals( 'buzz', $actual );
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
* Plugin Name: WooCommerce
|
||||
* Plugin URI: https://woocommerce.com/
|
||||
* Description: An eCommerce toolkit that helps you sell anything. Beautifully.
|
||||
* Version: 5.3.0-dev
|
||||
* Version: 5.4.0-dev
|
||||
* Author: Automattic
|
||||
* Author URI: https://woocommerce.com
|
||||
* Text Domain: woocommerce
|
||||
|
|
Loading…
Reference in New Issue