diff --git a/.github/workflows/smoke-test-daily.yml b/.github/workflows/smoke-test-daily.yml index b3468b1135c..671b88f158f 100644 --- a/.github/workflows/smoke-test-daily.yml +++ b/.github/workflows/smoke-test-daily.yml @@ -40,6 +40,7 @@ jobs: E2E_SLACK_TOKEN: ${{ secrets.SMOKE_TEST_SLACK_TOKEN }} E2E_SLACK_CHANNEL: ${{ secrets.SMOKE_TEST_SLACK_CHANNEL }} UPDATE_WC: 1 + DEFAULT_TIMEOUT_OVERRIDE: 120000 run: | npx wc-e2e test:e2e ./tests/e2e/specs/smoke-tests/update-woocommerce.js npx wc-e2e test:e2e diff --git a/.github/workflows/smoke-test-release.yml b/.github/workflows/smoke-test-release.yml new file mode 100644 index 00000000000..960f193b518 --- /dev/null +++ b/.github/workflows/smoke-test-release.yml @@ -0,0 +1,47 @@ +name: Smoke test release +on: + release: + types: [published] +jobs: + login-run: + name: Daily smoke test on release. + runs-on: ubuntu-18.04 + steps: + + - name: Create dirs. + run: | + mkdir -p code/woocommerce + mkdir -p package/woocommerce + mkdir -p tmp/woocommerce + mkdir -p node_modules + + - name: Checkout code. + uses: actions/checkout@v2 + with: + ref: trunk + + - name: Install prerequisites. + run: | + npm install + composer install --no-dev + npm run build:assets + npm install jest + + - name: Run smoke test. + env: + SMOKE_TEST_URL: ${{ secrets.RELEASE_TEST_URL }} + SMOKE_TEST_ADMIN_USER: ${{ secrets.RELEASE_TEST_ADMIN_USER }} + SMOKE_TEST_ADMIN_PASSWORD: ${{ secrets.RELEASE_TEST_ADMIN_PASSWORD }} + SMOKE_TEST_ADMIN_USER_EMAIL: ${{ secrets.RELEASE_TEST_ADMIN_USER_EMAIL }} + SMOKE_TEST_CUSTOMER_USER: ${{ secrets.RELEASE_TEST_CUSTOMER_USER }} + SMOKE_TEST_CUSTOMER_PASSWORD: ${{ secrets.RELEASE_TEST_CUSTOMER_PASSWORD }} + WC_E2E_SCREENSHOTS: 1 + E2E_RETEST: 1 + E2E_SLACK_TOKEN: ${{ secrets.SMOKE_TEST_SLACK_TOKEN }} + E2E_SLACK_CHANNEL: ${{ secrets.SMOKE_TEST_SLACK_CHANNEL }} + TEST_RELEASE: 1 + UPDATE_WC: 1 + DEFAULT_TIMEOUT_OVERRIDE: 120000 + run: | + npx wc-e2e test:e2e ./tests/e2e/specs/smoke-tests/update-woocommerce.js + npx wc-e2e test:e2e diff --git a/.github/workflows/stalebot.yml b/.github/workflows/stalebot.yml index 23c1a0eec6e..7257ea1b7ec 100644 --- a/.github/workflows/stalebot.yml +++ b/.github/workflows/stalebot.yml @@ -5,6 +5,8 @@ on: jobs: stale: + if: | + ! contains(github.event.issue.labels.*.name, 'enhancement') runs-on: ubuntu-latest steps: - uses: actions/stale@v3 diff --git a/assets/js/select2/select2.full.js b/assets/js/select2/select2.full.js index d0776a6022b..e750834ef5d 100644 --- a/assets/js/select2/select2.full.js +++ b/assets/js/select2/select2.full.js @@ -1,41 +1,27 @@ /*! - * SelectWoo 1.0.9 - * https://github.com/woocommerce/selectWoo + * Select2 4.0.3 + * https://select2.github.io * * Released under the MIT license - * https://github.com/woocommerce/selectWoo/blob/master/LICENSE.md + * https://github.com/select2/select2/blob/master/LICENSE.md */ (function (factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define(['jquery'], factory); - } else if (typeof module === 'object' && module.exports) { + } else if (typeof exports === 'object') { // Node/CommonJS - 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; - }; + factory(require('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) { @@ -44,11 +30,13 @@ var S2;(function () { if (!S2 || !S2.requirejs) { if (!S2) { S2 = {}; } else { require = S2; } /** - * @license almond 0.3.3 Copyright jQuery Foundation and other contributors. - * Released under MIT license, http://github.com/requirejs/almond/LICENSE + * @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 */ //Going sloppy to avoid 'use strict' string cost, but strict practices should //be followed. +/*jslint sloppy: true */ /*global setTimeout: false */ var requirejs, require, define; @@ -76,58 +64,60 @@ var requirejs, require, define; */ function normalize(name, baseName) { var nameParts, nameSegment, mapValue, foundMap, lastIndex, - foundI, foundStarMap, starI, i, j, part, normalizedBaseParts, + foundI, foundStarMap, starI, i, j, part, baseParts = baseName && baseName.split("/"), map = config.map, starMap = (map && map['*']) || {}; //Adjust any relative paths. - if (name) { - name = name.split('/'); - lastIndex = name.length - 1; + 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 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, ''); - } + // Node .js allowance: + if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) { + name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, ''); + } - // 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); - } + //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); - //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; + //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; + } } } - } - //end trimDots + //end trimDots - name = name.join('/'); + 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); + } } //Apply map config if available. @@ -240,39 +230,32 @@ 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, relParts) { + makeMap = function (name, relName) { var plugin, parts = splitPrefix(name), - prefix = parts[0], - relResourceName = relParts[1]; + prefix = parts[0]; name = parts[1]; if (prefix) { - prefix = normalize(prefix, relResourceName); + prefix = normalize(prefix, relName); plugin = callDep(prefix); } //Normalize according if (prefix) { if (plugin && plugin.normalize) { - name = plugin.normalize(name, makeNormalize(relResourceName)); + name = plugin.normalize(name, makeNormalize(relName)); } else { - name = normalize(name, relResourceName); + name = normalize(name, relName); } } else { - name = normalize(name, relResourceName); + name = normalize(name, relName); parts = splitPrefix(name); prefix = parts[0]; name = parts[1]; @@ -319,14 +302,13 @@ var requirejs, require, define; }; main = function (name, deps, callback, relName) { - var cjsModule, depName, ret, map, i, relParts, + var cjsModule, depName, ret, map, i, 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') { @@ -335,7 +317,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], relParts); + map = makeMap(deps[i], relName); depName = map.f; //Fast path CommonJS standard dependencies. @@ -391,7 +373,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, makeRelParts(callback)).f); + return callDep(makeMap(deps, callback).f); } else if (!deps.splice) { //deps is a config object, not an array. config = deps; @@ -755,12 +737,6 @@ 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 @@ -778,14 +754,6 @@ 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; }); @@ -805,7 +773,7 @@ S2.define('select2/results',[ Results.prototype.render = function () { var $results = $( - '' + '' ); if (this.options.get('multiple')) { @@ -828,7 +796,7 @@ S2.define('select2/results',[ this.hideLoading(); var $message = $( - '' ); @@ -890,9 +858,9 @@ S2.define('select2/results',[ Results.prototype.highlightFirstItem = function () { var $options = this.$results - .find('.select2-results__option[data-selected]'); + .find('.select2-results__option[aria-selected]'); - var $selected = $options.filter('[data-selected=true]'); + var $selected = $options.filter('[aria-selected=true]'); // Check if there are any selected options if ($selected.length > 0) { @@ -916,7 +884,7 @@ S2.define('select2/results',[ }); var $options = self.$results - .find('.select2-results__option[data-selected]'); + .find('.select2-results__option[aria-selected]'); $options.each(function () { var $option = $(this); @@ -928,9 +896,9 @@ S2.define('select2/results',[ if ((item.element != null && item.element.selected) || (item.element == null && $.inArray(id, selectedIds) > -1)) { - $option.attr('data-selected', 'true'); + $option.attr('aria-selected', 'true'); } else { - $option.attr('data-selected', 'false'); + $option.attr('aria-selected', 'false'); } }); @@ -962,18 +930,17 @@ S2.define('select2/results',[ option.className = 'select2-results__option'; var attrs = { - 'role': 'option', - 'data-selected': 'false', - 'tabindex': -1 + 'role': 'treeitem', + 'aria-selected': 'false' }; if (data.disabled) { - delete attrs['data-selected']; + delete attrs['aria-selected']; attrs['aria-disabled'] = 'true'; } if (data.id == null) { - delete attrs['data-selected']; + delete attrs['aria-selected']; } if (data._resultId != null) { @@ -985,8 +952,9 @@ S2.define('select2/results',[ } if (data.children) { + attrs.role = 'group'; attrs['aria-label'] = data.text; - delete attrs['data-selected']; + delete attrs['aria-selected']; } for (var attr in attrs) { @@ -1003,7 +971,6 @@ S2.define('select2/results',[ var $label = $(label); this.template(data, label); - $label.attr('role', 'presentation'); var $children = []; @@ -1016,11 +983,10 @@ S2.define('select2/results',[ } var $childrenContainer = $('', { - 'class': 'select2-results__options select2-results__options--nested', - 'role': 'listbox' + 'class': 'select2-results__options select2-results__options--nested' }); + $childrenContainer.append($children); - $option.attr('role', 'list'); $option.append(label); $option.append($childrenContainer); @@ -1116,7 +1082,7 @@ S2.define('select2/results',[ var data = $highlighted.data('data'); - if ($highlighted.attr('data-selected') == 'true') { + if ($highlighted.attr('aria-selected') == 'true') { self.trigger('close', {}); } else { self.trigger('select', { @@ -1128,7 +1094,7 @@ S2.define('select2/results',[ container.on('results:previous', function () { var $highlighted = self.getHighlightedResults(); - var $options = self.$results.find('[data-selected]'); + var $options = self.$results.find('[aria-selected]'); var currentIndex = $options.index($highlighted); @@ -1162,7 +1128,7 @@ S2.define('select2/results',[ container.on('results:next', function () { var $highlighted = self.getHighlightedResults(); - var $options = self.$results.find('[data-selected]'); + var $options = self.$results.find('[aria-selected]'); var currentIndex = $options.index($highlighted); @@ -1190,8 +1156,7 @@ S2.define('select2/results',[ }); container.on('results:focus', function (params) { - params.element.addClass('select2-results__option--highlighted').attr('aria-selected', 'true'); - self.$results.attr('aria-activedescendant', params.element.attr('id')); + params.element.addClass('select2-results__option--highlighted'); }); container.on('results:message', function (params) { @@ -1223,13 +1188,13 @@ S2.define('select2/results',[ }); } - this.$results.on('mouseup', '.select2-results__option[data-selected]', + this.$results.on('mouseup', '.select2-results__option[aria-selected]', function (evt) { var $this = $(this); var data = $this.data('data'); - if ($this.attr('data-selected') === 'true') { + if ($this.attr('aria-selected') === 'true') { if (self.options.get('multiple')) { self.trigger('unselect', { originalEvent: evt, @@ -1248,13 +1213,12 @@ S2.define('select2/results',[ }); }); - this.$results.on('mouseenter', '.select2-results__option[data-selected]', + this.$results.on('mouseenter', '.select2-results__option[aria-selected]', function (evt) { var data = $(this).data('data'); self.getHighlightedResults() - .removeClass('select2-results__option--highlighted') - .attr('aria-selected', 'false'); + .removeClass('select2-results__option--highlighted'); self.trigger('results:focus', { data: data, @@ -1281,7 +1245,7 @@ S2.define('select2/results',[ return; } - var $options = this.$results.find('[data-selected]'); + var $options = this.$results.find('[aria-selected]'); var currentIndex = $options.index($highlighted); @@ -1359,7 +1323,7 @@ S2.define('select2/selection/base',[ BaseSelection.prototype.render = function () { var $selection = $( - '' ); @@ -1385,7 +1349,6 @@ 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; @@ -1427,11 +1390,7 @@ S2.define('select2/selection/base',[ self.$selection.removeAttr('aria-activedescendant'); self.$selection.removeAttr('aria-owns'); - // This needs to be delayed as the active element is the body when the - // key is pressed. - window.setTimeout(function () { - self.$selection.trigger( 'focus' ); - }, 1); + self.$selection.focus(); self._detachCloseHandler(container); }); @@ -1481,14 +1440,8 @@ 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); + $element.select2('close'); }); }); }; @@ -1547,21 +1500,8 @@ S2.define('select2/selection/single',[ var id = container.id + '-container'; - 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.find('.select2-selection__rendered').attr('id', id); + this.$selection.attr('aria-labelledby', id); this.$selection.on('mousedown', function (evt) { // Only respond to left clicks @@ -1578,20 +1518,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 }); container.on('focus', function (evt) { if (!container.isOpen()) { - self.$selection.trigger( 'focus' ); + self.$selection.focus(); } }); @@ -1624,9 +1557,9 @@ S2.define('select2/selection/single',[ var selection = data[0]; var $rendered = this.$selection.find('.select2-selection__rendered'); - var formatted = Utils.entityDecode(this.display(selection, $rendered)); + var formatted = this.display(selection, $rendered); - $rendered.empty().text(formatted); + $rendered.empty().append(formatted); $rendered.prop('title', selection.title || selection.text); }; @@ -1650,7 +1583,7 @@ S2.define('select2/selection/multiple',[ $selection.addClass('select2-selection--multiple'); $selection.html( - '' + '' ); return $selection; @@ -1687,18 +1620,6 @@ 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 () { @@ -1715,7 +1636,7 @@ S2.define('select2/selection/multiple',[ MultipleSelection.prototype.selectionContainer = function () { var $container = $( '
  • ' + - '
  • ' @@ -1724,24 +1645,6 @@ 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(); @@ -1755,14 +1658,9 @@ 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.text(formatted); - $selection.prepend(removeItemTag); + $selection.append(formatted); $selection.prop('title', selection.title || selection.text); $selection.data('data', selection); @@ -1801,7 +1699,7 @@ S2.define('select2/selection/placeholder',[ Placeholder.prototype.createPlaceholder = function (decorated, placeholder) { var $placeholder = this.selectionContainer(); - $placeholder.text(Utils.entityDecode(this.display(placeholder))); + $placeholder.html(this.display(placeholder)); $placeholder.addClass('select2-selection__placeholder') .removeClass('select2-selection__choice'); @@ -1938,8 +1836,8 @@ S2.define('select2/selection/search',[ Search.prototype.render = function (decorated) { var $search = $( '' ); @@ -1956,19 +1854,16 @@ 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'); }); @@ -1987,7 +1882,7 @@ S2.define('select2/selection/search',[ }); container.on('results:focus', function (params) { - self.$search.attr('aria-activedescendant', params.data._resultId); + self.$search.attr('aria-activedescendant', params.id); }); this.$selection.on('focusin', '.select2-search--inline', function (evt) { @@ -2018,9 +1913,6 @@ S2.define('select2/selection/search',[ evt.preventDefault(); } - } else if (evt.which === KEYS.ENTER) { - container.open(); - evt.preventDefault(); } }); @@ -2109,7 +2001,7 @@ S2.define('select2/selection/search',[ this.resizeSearch(); if (searchHadFocus) { - this.$search.trigger( 'focus' ); + this.$search.focus(); } }; @@ -3112,15 +3004,8 @@ S2.define('select2/data/base',[ }; BaseAdapter.prototype.generateResultId = function (container, data) { - var id = ''; + var id = container.id + '-result-'; - if (container != null) { - id += container.id - } else { - id += Utils.generateChars(4); - } - - id += '-result-'; id += Utils.generateChars(4); if (data.id != null) { @@ -3306,7 +3191,7 @@ S2.define('select2/data/select',[ } } - if (data.id !== undefined) { + if (data.id) { option.value = data.id; } @@ -3404,7 +3289,7 @@ S2.define('select2/data/select',[ item.text = item.text.toString(); } - if (item._resultId == null && item.id) { + if (item._resultId == null && item.id && this.container != null) { item._resultId = this.generateResultId(this.container, item); } @@ -3547,7 +3432,7 @@ S2.define('select2/data/ajax',[ if (this._request != null) { // JSONP requests cannot always be aborted - if ( typeof this._request.abort === 'function' ) { + if ($.isFunction(this._request.abort)) { this._request.abort(); } @@ -3572,7 +3457,7 @@ S2.define('select2/data/ajax',[ if (self.options.get('debug') && window.console && console.error) { // Check to make sure that the response included a `results` key. - if (!results || !results.results || ! Array.isArray( results.results ) ) { + if (!results || !results.results || !$.isArray(results.results)) { console.error( 'Select2: The AJAX results did not return an array in the ' + '`results` key of the response.' @@ -3581,7 +3466,6 @@ 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 @@ -3631,7 +3515,7 @@ S2.define('select2/data/tags',[ decorated.call(this, $element, options); - if ( Array.isArray( tags ) ) { + if ($.isArray(tags)) { for (var t = 0; t < tags.length; t++) { var tag = tags[t]; var item = this._normalizeItem(tag); @@ -3666,10 +3550,7 @@ S2.define('select2/data/tags',[ }, true) ); - var optionText = (option.text || '').toUpperCase(); - var paramsTerm = (params.term || '').toUpperCase(); - - var checkText = optionText === paramsTerm; + var checkText = option.text === params.term; if (checkText || checkChildren) { if (child) { @@ -3707,7 +3588,7 @@ S2.define('select2/data/tags',[ }; Tags.prototype.createTag = function (decorated, params) { - var term = ( params.term || 0 == params.term ) ? params.term.toString().trim() : ''; + var term = $.trim(params.term); if (term === '') { return null; @@ -3800,7 +3681,7 @@ S2.define('select2/data/tokenizer',[ // Replace the search term if we have the search box if (this.$search.length) { this.$search.val(tokenData.term); - this.$search.trigger( 'focus' ); + this.$search.focus(); } params.term = tokenData.term; @@ -4006,9 +3887,9 @@ S2.define('select2/dropdown/search',[ var $search = $( '' + - '' + + '' + '' ); @@ -4022,7 +3903,6 @@ 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); @@ -4046,24 +3926,23 @@ S2.define('select2/dropdown/search',[ container.on('open', function () { self.$search.attr('tabindex', 0); - self.$search.attr('aria-owns', resultsId); - self.$search.trigger( 'focus' ); + + self.$search.focus(); window.setTimeout(function () { - self.$search.trigger( 'focus' ); + self.$search.focus(); }, 0); }); 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()) { - self.$search.trigger( 'focus' ); + if (container.isOpen()) { + self.$search.focus(); } }); @@ -4078,10 +3957,6 @@ S2.define('select2/dropdown/search',[ } } }); - - container.on('results:focus', function (params) { - self.$search.attr('aria-activedescendant', params.data._resultId); - }); }; Search.prototype.handleSearch = function (evt) { @@ -4223,7 +4098,7 @@ S2.define('select2/dropdown/infiniteScroll',[ var $option = $( '
  • ' + 'role="treeitem" aria-disabled="true">' ); var message = this.options.get('translations').get('loadingMore'); @@ -4878,7 +4753,7 @@ S2.define('select2/defaults',[ } } - if ( Array.isArray( options.language ) ) { + if ($.isArray(options.language)) { var languages = new Translation(); options.language.push('en'); @@ -4941,7 +4816,7 @@ S2.define('select2/defaults',[ function matcher (params, data) { // Always return the object if there is nothing to compare - if ( params.term == null || params.term.toString().trim() === '' ) { + if ($.trim(params.term) === '') { return data; } @@ -5469,22 +5344,16 @@ S2.define('select2/core',[ }); }); - 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) { + this.on('keypress', function (evt) { var key = evt.which; + if (self.isOpen()) { - if (key === KEYS.ESC || (key === KEYS.UP && evt.altKey)) { + if (key === KEYS.ESC || key === KEYS.TAB || + (key === KEYS.UP && evt.altKey)) { self.close(); evt.preventDefault(); - } else if (key === KEYS.ENTER || key === KEYS.TAB) { + } else if (key === KEYS.ENTER) { self.trigger('results:select', {}); evt.preventDefault(); @@ -5501,42 +5370,17 @@ S2.define('select2/core',[ evt.preventDefault(); } - - 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()) { + } else { if (key === KEYS.ENTER || key === KEYS.SPACE || - key === KEYS.DOWN) { + (key === KEYS.DOWN && evt.altKey)) { 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')); @@ -5724,7 +5568,7 @@ S2.define('select2/core',[ var newVal = args[0]; - if ( Array.isArray( newVal ) ) { + if ($.isArray(newVal)) { newVal = $.map(newVal, function (obj) { return obj.toString(); }); @@ -5801,7 +5645,7 @@ S2.define('select2/compat/utils',[ function syncCssClasses ($dest, $src, adapter) { var classes, replacements = [], adapted; - classes = ( $dest.attr('class') || 0 == $dest.attr('class') ) ? $dest.attr('class').toString().trim() : ''; + classes = $.trim($dest.attr('class')); if (classes) { classes = '' + classes; // for IE which returns object @@ -5814,7 +5658,7 @@ S2.define('select2/compat/utils',[ }); } - classes = ( $src.attr('class') || 0 == $src.attr('class') ) ? $src.attr('class').toString().trim() : ''; + classes = $.trim($src.attr('class')); if (classes) { classes = '' + classes; // for IE which returns object @@ -5855,7 +5699,7 @@ S2.define('select2/compat/containerCss',[ var containerCssClass = this.options.get('containerCssClass') || ''; - if ( typeof containerCssClass === 'function' ) { + if ($.isFunction(containerCssClass)) { containerCssClass = containerCssClass(this.$element); } @@ -5881,7 +5725,7 @@ S2.define('select2/compat/containerCss',[ var containerCss = this.options.get('containerCss') || {}; - if ( typeof containerCss === 'function' ) { + if ($.isFunction(containerCss)) { containerCss = containerCss(this.$element); } @@ -5912,7 +5756,7 @@ S2.define('select2/compat/dropdownCss',[ var dropdownCssClass = this.options.get('dropdownCssClass') || ''; - if ( typeof dropdownCssClass === 'function' ) { + if ($.isFunction(dropdownCssClass)) { dropdownCssClass = dropdownCssClass(this.$element); } @@ -5938,7 +5782,7 @@ S2.define('select2/compat/dropdownCss',[ var dropdownCss = this.options.get('dropdownCss') || {}; - if ( typeof dropdownCss === 'function' ) { + if ($.isFunction(dropdownCss)) { dropdownCss = dropdownCss(this.$element); } @@ -5985,7 +5829,7 @@ S2.define('select2/compat/initSelection',[ this.initSelection.call(null, this.$element, function (data) { self._isInitialized = true; - if ( ! Array.isArray( data ) ) { + if (!$.isArray(data)) { data = [data]; } @@ -6131,7 +5975,7 @@ S2.define('select2/compat/matcher',[ function wrappedMatcher (params, data) { var match = $.extend(true, {}, data); - if ( params.term == null || params.term.trim() === '' ) { + if (params.term == null || $.trim(params.term) === '') { return match; } @@ -6374,11 +6218,11 @@ S2.define('select2/selection/stopPropagation',[ $.fn.extend({ mousewheel: function(fn) { - return fn ? this.on('mousewheel', fn) : this.trigger('mousewheel'); + return fn ? this.bind('mousewheel', fn) : this.trigger('mousewheel'); }, unmousewheel: function(fn) { - return this.off('mousewheel', fn); + return this.unbind('mousewheel', fn); } }); @@ -6520,11 +6364,11 @@ S2.define('jquery.select2',[ './select2/core', './select2/defaults' ], function ($, _, Select2, Defaults) { - if ($.fn.selectWoo == null) { + if ($.fn.select2 == null) { // All methods that should return the element var thisMethods = ['open', 'close', 'destroy']; - $.fn.selectWoo = function (options) { + $.fn.select2 = function (options) { options = options || {}; if (typeof options === 'object') { @@ -6564,17 +6408,10 @@ S2.define('jquery.select2',[ }; } - if ($.fn.select2 != null && $.fn.select2.defaults != null) { - $.fn.selectWoo.defaults = $.fn.select2.defaults; + if ($.fn.select2.defaults == null) { + $.fn.select2.defaults = 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; }); @@ -6593,7 +6430,6 @@ 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; diff --git a/assets/js/select2/select2.js b/assets/js/select2/select2.js index f0e059f17d3..13b84fadff6 100644 --- a/assets/js/select2/select2.js +++ b/assets/js/select2/select2.js @@ -1,41 +1,27 @@ /*! - * SelectWoo 1.0.9 - * https://github.com/woocommerce/selectWoo + * Select2 4.0.3 + * https://select2.github.io * * Released under the MIT license - * https://github.com/woocommerce/selectWoo/blob/master/LICENSE.md + * https://github.com/select2/select2/blob/master/LICENSE.md */ (function (factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define(['jquery'], factory); - } else if (typeof module === 'object' && module.exports) { + } else if (typeof exports === 'object') { // Node/CommonJS - 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; - }; + factory(require('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) { @@ -44,11 +30,13 @@ var S2;(function () { if (!S2 || !S2.requirejs) { if (!S2) { S2 = {}; } else { require = S2; } /** - * @license almond 0.3.3 Copyright jQuery Foundation and other contributors. - * Released under MIT license, http://github.com/requirejs/almond/LICENSE + * @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 */ //Going sloppy to avoid 'use strict' string cost, but strict practices should //be followed. +/*jslint sloppy: true */ /*global setTimeout: false */ var requirejs, require, define; @@ -76,58 +64,60 @@ var requirejs, require, define; */ function normalize(name, baseName) { var nameParts, nameSegment, mapValue, foundMap, lastIndex, - foundI, foundStarMap, starI, i, j, part, normalizedBaseParts, + foundI, foundStarMap, starI, i, j, part, baseParts = baseName && baseName.split("/"), map = config.map, starMap = (map && map['*']) || {}; //Adjust any relative paths. - if (name) { - name = name.split('/'); - lastIndex = name.length - 1; + 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 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, ''); - } + // Node .js allowance: + if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) { + name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, ''); + } - // 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); - } + //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); - //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; + //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; + } } } - } - //end trimDots + //end trimDots - name = name.join('/'); + 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); + } } //Apply map config if available. @@ -240,39 +230,32 @@ 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, relParts) { + makeMap = function (name, relName) { var plugin, parts = splitPrefix(name), - prefix = parts[0], - relResourceName = relParts[1]; + prefix = parts[0]; name = parts[1]; if (prefix) { - prefix = normalize(prefix, relResourceName); + prefix = normalize(prefix, relName); plugin = callDep(prefix); } //Normalize according if (prefix) { if (plugin && plugin.normalize) { - name = plugin.normalize(name, makeNormalize(relResourceName)); + name = plugin.normalize(name, makeNormalize(relName)); } else { - name = normalize(name, relResourceName); + name = normalize(name, relName); } } else { - name = normalize(name, relResourceName); + name = normalize(name, relName); parts = splitPrefix(name); prefix = parts[0]; name = parts[1]; @@ -319,14 +302,13 @@ var requirejs, require, define; }; main = function (name, deps, callback, relName) { - var cjsModule, depName, ret, map, i, relParts, + var cjsModule, depName, ret, map, i, 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') { @@ -335,7 +317,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], relParts); + map = makeMap(deps[i], relName); depName = map.f; //Fast path CommonJS standard dependencies. @@ -391,7 +373,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, makeRelParts(callback)).f); + return callDep(makeMap(deps, callback).f); } else if (!deps.splice) { //deps is a config object, not an array. config = deps; @@ -755,12 +737,6 @@ 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 @@ -778,14 +754,6 @@ 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; }); @@ -805,7 +773,7 @@ S2.define('select2/results',[ Results.prototype.render = function () { var $results = $( - '' + '' ); if (this.options.get('multiple')) { @@ -828,7 +796,7 @@ S2.define('select2/results',[ this.hideLoading(); var $message = $( - '' ); @@ -890,9 +858,9 @@ S2.define('select2/results',[ Results.prototype.highlightFirstItem = function () { var $options = this.$results - .find('.select2-results__option[data-selected]'); + .find('.select2-results__option[aria-selected]'); - var $selected = $options.filter('[data-selected=true]'); + var $selected = $options.filter('[aria-selected=true]'); // Check if there are any selected options if ($selected.length > 0) { @@ -916,7 +884,7 @@ S2.define('select2/results',[ }); var $options = self.$results - .find('.select2-results__option[data-selected]'); + .find('.select2-results__option[aria-selected]'); $options.each(function () { var $option = $(this); @@ -928,9 +896,9 @@ S2.define('select2/results',[ if ((item.element != null && item.element.selected) || (item.element == null && $.inArray(id, selectedIds) > -1)) { - $option.attr('data-selected', 'true'); + $option.attr('aria-selected', 'true'); } else { - $option.attr('data-selected', 'false'); + $option.attr('aria-selected', 'false'); } }); @@ -962,18 +930,17 @@ S2.define('select2/results',[ option.className = 'select2-results__option'; var attrs = { - 'role': 'option', - 'data-selected': 'false', - 'tabindex': -1 + 'role': 'treeitem', + 'aria-selected': 'false' }; if (data.disabled) { - delete attrs['data-selected']; + delete attrs['aria-selected']; attrs['aria-disabled'] = 'true'; } if (data.id == null) { - delete attrs['data-selected']; + delete attrs['aria-selected']; } if (data._resultId != null) { @@ -985,8 +952,9 @@ S2.define('select2/results',[ } if (data.children) { + attrs.role = 'group'; attrs['aria-label'] = data.text; - delete attrs['data-selected']; + delete attrs['aria-selected']; } for (var attr in attrs) { @@ -1003,7 +971,6 @@ S2.define('select2/results',[ var $label = $(label); this.template(data, label); - $label.attr('role', 'presentation'); var $children = []; @@ -1016,11 +983,10 @@ S2.define('select2/results',[ } var $childrenContainer = $('', { - 'class': 'select2-results__options select2-results__options--nested', - 'role': 'listbox' + 'class': 'select2-results__options select2-results__options--nested' }); + $childrenContainer.append($children); - $option.attr('role', 'list'); $option.append(label); $option.append($childrenContainer); @@ -1116,7 +1082,7 @@ S2.define('select2/results',[ var data = $highlighted.data('data'); - if ($highlighted.attr('data-selected') == 'true') { + if ($highlighted.attr('aria-selected') == 'true') { self.trigger('close', {}); } else { self.trigger('select', { @@ -1128,7 +1094,7 @@ S2.define('select2/results',[ container.on('results:previous', function () { var $highlighted = self.getHighlightedResults(); - var $options = self.$results.find('[data-selected]'); + var $options = self.$results.find('[aria-selected]'); var currentIndex = $options.index($highlighted); @@ -1162,7 +1128,7 @@ S2.define('select2/results',[ container.on('results:next', function () { var $highlighted = self.getHighlightedResults(); - var $options = self.$results.find('[data-selected]'); + var $options = self.$results.find('[aria-selected]'); var currentIndex = $options.index($highlighted); @@ -1190,8 +1156,7 @@ S2.define('select2/results',[ }); container.on('results:focus', function (params) { - params.element.addClass('select2-results__option--highlighted').attr('aria-selected', 'true'); - self.$results.attr('aria-activedescendant', params.element.attr('id')); + params.element.addClass('select2-results__option--highlighted'); }); container.on('results:message', function (params) { @@ -1223,13 +1188,13 @@ S2.define('select2/results',[ }); } - this.$results.on('mouseup', '.select2-results__option[data-selected]', + this.$results.on('mouseup', '.select2-results__option[aria-selected]', function (evt) { var $this = $(this); var data = $this.data('data'); - if ($this.attr('data-selected') === 'true') { + if ($this.attr('aria-selected') === 'true') { if (self.options.get('multiple')) { self.trigger('unselect', { originalEvent: evt, @@ -1248,13 +1213,12 @@ S2.define('select2/results',[ }); }); - this.$results.on('mouseenter', '.select2-results__option[data-selected]', + this.$results.on('mouseenter', '.select2-results__option[aria-selected]', function (evt) { var data = $(this).data('data'); self.getHighlightedResults() - .removeClass('select2-results__option--highlighted') - .attr('aria-selected', 'false'); + .removeClass('select2-results__option--highlighted'); self.trigger('results:focus', { data: data, @@ -1281,7 +1245,7 @@ S2.define('select2/results',[ return; } - var $options = this.$results.find('[data-selected]'); + var $options = this.$results.find('[aria-selected]'); var currentIndex = $options.index($highlighted); @@ -1359,7 +1323,7 @@ S2.define('select2/selection/base',[ BaseSelection.prototype.render = function () { var $selection = $( - '' ); @@ -1385,7 +1349,6 @@ 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; @@ -1427,11 +1390,7 @@ S2.define('select2/selection/base',[ self.$selection.removeAttr('aria-activedescendant'); self.$selection.removeAttr('aria-owns'); - // This needs to be delayed as the active element is the body when the - // key is pressed. - window.setTimeout(function () { - self.$selection.trigger( 'focus' ); - }, 1); + self.$selection.focus(); self._detachCloseHandler(container); }); @@ -1481,14 +1440,8 @@ 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); + $element.select2('close'); }); }); }; @@ -1547,21 +1500,8 @@ S2.define('select2/selection/single',[ var id = container.id + '-container'; - 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.find('.select2-selection__rendered').attr('id', id); + this.$selection.attr('aria-labelledby', id); this.$selection.on('mousedown', function (evt) { // Only respond to left clicks @@ -1578,20 +1518,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 }); container.on('focus', function (evt) { if (!container.isOpen()) { - self.$selection.trigger( 'focus' ); + self.$selection.focus(); } }); @@ -1624,9 +1557,9 @@ S2.define('select2/selection/single',[ var selection = data[0]; var $rendered = this.$selection.find('.select2-selection__rendered'); - var formatted = Utils.entityDecode(this.display(selection, $rendered)); + var formatted = this.display(selection, $rendered); - $rendered.empty().text(formatted); + $rendered.empty().append(formatted); $rendered.prop('title', selection.title || selection.text); }; @@ -1650,7 +1583,7 @@ S2.define('select2/selection/multiple',[ $selection.addClass('select2-selection--multiple'); $selection.html( - '' + '' ); return $selection; @@ -1687,18 +1620,6 @@ 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 () { @@ -1715,7 +1636,7 @@ S2.define('select2/selection/multiple',[ MultipleSelection.prototype.selectionContainer = function () { var $container = $( '
  • ' + - '
  • ' @@ -1724,24 +1645,6 @@ 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(); @@ -1755,14 +1658,9 @@ 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.text(formatted); - $selection.prepend(removeItemTag); + $selection.append(formatted); $selection.prop('title', selection.title || selection.text); $selection.data('data', selection); @@ -1801,7 +1699,7 @@ S2.define('select2/selection/placeholder',[ Placeholder.prototype.createPlaceholder = function (decorated, placeholder) { var $placeholder = this.selectionContainer(); - $placeholder.text(Utils.entityDecode(this.display(placeholder))); + $placeholder.html(this.display(placeholder)); $placeholder.addClass('select2-selection__placeholder') .removeClass('select2-selection__choice'); @@ -1938,8 +1836,8 @@ S2.define('select2/selection/search',[ Search.prototype.render = function (decorated) { var $search = $( '' ); @@ -1956,19 +1854,16 @@ 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'); }); @@ -1987,7 +1882,7 @@ S2.define('select2/selection/search',[ }); container.on('results:focus', function (params) { - self.$search.attr('aria-activedescendant', params.data._resultId); + self.$search.attr('aria-activedescendant', params.id); }); this.$selection.on('focusin', '.select2-search--inline', function (evt) { @@ -2018,9 +1913,6 @@ S2.define('select2/selection/search',[ evt.preventDefault(); } - } else if (evt.which === KEYS.ENTER) { - container.open(); - evt.preventDefault(); } }); @@ -2109,7 +2001,7 @@ S2.define('select2/selection/search',[ this.resizeSearch(); if (searchHadFocus) { - this.$search.trigger( 'focus' ); + this.$search.focus(); } }; @@ -3112,15 +3004,8 @@ S2.define('select2/data/base',[ }; BaseAdapter.prototype.generateResultId = function (container, data) { - var id = ''; + var id = container.id + '-result-'; - if (container != null) { - id += container.id - } else { - id += Utils.generateChars(4); - } - - id += '-result-'; id += Utils.generateChars(4); if (data.id != null) { @@ -3306,7 +3191,7 @@ S2.define('select2/data/select',[ } } - if (data.id !== undefined) { + if (data.id) { option.value = data.id; } @@ -3404,7 +3289,7 @@ S2.define('select2/data/select',[ item.text = item.text.toString(); } - if (item._resultId == null && item.id) { + if (item._resultId == null && item.id && this.container != null) { item._resultId = this.generateResultId(this.container, item); } @@ -3547,7 +3432,7 @@ S2.define('select2/data/ajax',[ if (this._request != null) { // JSONP requests cannot always be aborted - if ( typeof this._request.abort === 'function' ) { + if ($.isFunction(this._request.abort)) { this._request.abort(); } @@ -3572,7 +3457,7 @@ S2.define('select2/data/ajax',[ if (self.options.get('debug') && window.console && console.error) { // Check to make sure that the response included a `results` key. - if (!results || !results.results || ! Array.isArray( results.results ) ) { + if (!results || !results.results || !$.isArray(results.results)) { console.error( 'Select2: The AJAX results did not return an array in the ' + '`results` key of the response.' @@ -3581,7 +3466,6 @@ 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 @@ -3631,7 +3515,7 @@ S2.define('select2/data/tags',[ decorated.call(this, $element, options); - if ( Array.isArray( tags ) ) { + if ($.isArray(tags)) { for (var t = 0; t < tags.length; t++) { var tag = tags[t]; var item = this._normalizeItem(tag); @@ -3666,10 +3550,7 @@ S2.define('select2/data/tags',[ }, true) ); - var optionText = (option.text || '').toUpperCase(); - var paramsTerm = (params.term || '').toUpperCase(); - - var checkText = optionText === paramsTerm; + var checkText = option.text === params.term; if (checkText || checkChildren) { if (child) { @@ -3707,7 +3588,7 @@ S2.define('select2/data/tags',[ }; Tags.prototype.createTag = function (decorated, params) { - var term = ( params.term || 0 == params.term ) ? params.term.toString().trim() : ''; + var term = $.trim(params.term); if (term === '') { return null; @@ -3800,7 +3681,7 @@ S2.define('select2/data/tokenizer',[ // Replace the search term if we have the search box if (this.$search.length) { this.$search.val(tokenData.term); - this.$search.trigger( 'focus' ); + this.$search.focus(); } params.term = tokenData.term; @@ -4006,9 +3887,9 @@ S2.define('select2/dropdown/search',[ var $search = $( '' + - '' + + '' + '' ); @@ -4022,7 +3903,6 @@ 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); @@ -4046,24 +3926,23 @@ S2.define('select2/dropdown/search',[ container.on('open', function () { self.$search.attr('tabindex', 0); - self.$search.attr('aria-owns', resultsId); - self.$search.trigger( 'focus' ); + + self.$search.focus(); window.setTimeout(function () { - self.$search.trigger( 'focus' ); + self.$search.focus(); }, 0); }); 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()) { - self.$search.trigger( 'focus' ); + if (container.isOpen()) { + self.$search.focus(); } }); @@ -4078,10 +3957,6 @@ S2.define('select2/dropdown/search',[ } } }); - - container.on('results:focus', function (params) { - self.$search.attr('aria-activedescendant', params.data._resultId); - }); }; Search.prototype.handleSearch = function (evt) { @@ -4223,7 +4098,7 @@ S2.define('select2/dropdown/infiniteScroll',[ var $option = $( '
  • ' + 'role="treeitem" aria-disabled="true">' ); var message = this.options.get('translations').get('loadingMore'); @@ -4878,7 +4753,7 @@ S2.define('select2/defaults',[ } } - if ( Array.isArray( options.language ) ) { + if ($.isArray(options.language)) { var languages = new Translation(); options.language.push('en'); @@ -4941,7 +4816,7 @@ S2.define('select2/defaults',[ function matcher (params, data) { // Always return the object if there is nothing to compare - if ( params.term == null || params.term.toString().trim() === '' ) { + if ($.trim(params.term) === '') { return data; } @@ -5469,22 +5344,16 @@ S2.define('select2/core',[ }); }); - 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) { + this.on('keypress', function (evt) { var key = evt.which; + if (self.isOpen()) { - if (key === KEYS.ESC || (key === KEYS.UP && evt.altKey)) { + if (key === KEYS.ESC || key === KEYS.TAB || + (key === KEYS.UP && evt.altKey)) { self.close(); evt.preventDefault(); - } else if (key === KEYS.ENTER || key === KEYS.TAB) { + } else if (key === KEYS.ENTER) { self.trigger('results:select', {}); evt.preventDefault(); @@ -5501,42 +5370,17 @@ S2.define('select2/core',[ evt.preventDefault(); } - - 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()) { + } else { if (key === KEYS.ENTER || key === KEYS.SPACE || - key === KEYS.DOWN) { + (key === KEYS.DOWN && evt.altKey)) { 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')); @@ -5724,7 +5568,7 @@ S2.define('select2/core',[ var newVal = args[0]; - if ( Array.isArray( newVal ) ) { + if ($.isArray(newVal)) { newVal = $.map(newVal, function (obj) { return obj.toString(); }); @@ -5809,11 +5653,11 @@ S2.define('jquery.select2',[ './select2/core', './select2/defaults' ], function ($, _, Select2, Defaults) { - if ($.fn.selectWoo == null) { + if ($.fn.select2 == null) { // All methods that should return the element var thisMethods = ['open', 'close', 'destroy']; - $.fn.selectWoo = function (options) { + $.fn.select2 = function (options) { options = options || {}; if (typeof options === 'object') { @@ -5853,17 +5697,10 @@ S2.define('jquery.select2',[ }; } - if ($.fn.select2 != null && $.fn.select2.defaults != null) { - $.fn.selectWoo.defaults = $.fn.select2.defaults; + if ($.fn.select2.defaults == null) { + $.fn.select2.defaults = 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; }); @@ -5882,7 +5719,6 @@ 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; diff --git a/bin/composer/mozart/composer.lock b/bin/composer/mozart/composer.lock index 16cc746aa59..370960409ea 100644 --- a/bin/composer/mozart/composer.lock +++ b/bin/composer/mozart/composer.lock @@ -68,16 +68,16 @@ }, { "name": "league/flysystem", - "version": "1.1.4", + "version": "1.1.5", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "f3ad69181b8afed2c9edf7be5a2918144ff4ea32" + "reference": "18634df356bfd4119fe3d6156bdb990c414c14ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/f3ad69181b8afed2c9edf7be5a2918144ff4ea32", - "reference": "f3ad69181b8afed2c9edf7be5a2918144ff4ea32", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/18634df356bfd4119fe3d6156bdb990c414c14ea", + "reference": "18634df356bfd4119fe3d6156bdb990c414c14ea", "shasum": "" }, "require": { @@ -150,7 +150,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/1.1.4" + "source": "https://github.com/thephpleague/flysystem/tree/1.1.5" }, "funding": [ { @@ -158,7 +158,7 @@ "type": "other" } ], - "time": "2021-06-23T21:56:05+00:00" + "time": "2021-08-17T13:49:42+00:00" }, { "name": "league/mime-type-detection", diff --git a/changelog.txt b/changelog.txt index f6530befb10..1efc063b75a 100644 --- a/changelog.txt +++ b/changelog.txt @@ -25,7 +25,7 @@ * Dev - Apply `woocommerce_logout_default_redirect_url` filter to logout for custom endpoint. #29967 * Dev - Added new `woocommerce_email_sent` hook. #30123 -**WooCommerce Admin - 2.5.0** +**WooCommerce Admin - 2.5.0 & 2.5.1** - Add - Add a delete option to completed tasks #7300 - Add - Add unit tests around extended payment gateway controller #7133 @@ -79,6 +79,7 @@ - Fix - Fix analytics overview re-arrangement on initial load. #7475 - Fix - Fixes action button mis-alignment within card footer. #7412 - Fix - Fix up onboarding profiler not working when opted out of tracking +- Fix - Fix blank screen by setting a default value #7506 - Tweak - Remove performance indicators when Analytics Flag disabled #7234 - Tweak - Change event name when installing Google Listings and Ads. #7276 - Tweak - Removed unused feature flags #7233 and #7273 diff --git a/composer.json b/composer.json index 00dc37247c8..8220b72d415 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "pelago/emogrifier": "3.1.0", "psr/container": "1.0.0", "woocommerce/action-scheduler": "3.2.1", - "woocommerce/woocommerce-admin": "2.5.0", + "woocommerce/woocommerce-admin": "2.5.1", "woocommerce/woocommerce-blocks": "5.5.1" }, "require-dev": { diff --git a/composer.lock b/composer.lock index da812e039d0..e17936d9d88 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "e66790f6451aac2d4cb8f418bb26268e", + "content-hash": "f1450dbdfedc77735b411ae8a019b534", "packages": [ { "name": "automattic/jetpack-autoloader", @@ -532,16 +532,16 @@ }, { "name": "woocommerce/woocommerce-admin", - "version": "2.5.0", + "version": "2.5.1", "source": { "type": "git", "url": "https://github.com/woocommerce/woocommerce-admin.git", - "reference": "99a0bd3a48bf33a054efd3b0df29bf42766761f8" + "reference": "093d698d770f49d41df8abe89a716dc897bb9e96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/99a0bd3a48bf33a054efd3b0df29bf42766761f8", - "reference": "99a0bd3a48bf33a054efd3b0df29bf42766761f8", + "url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/093d698d770f49d41df8abe89a716dc897bb9e96", + "reference": "093d698d770f49d41df8abe89a716dc897bb9e96", "shasum": "" }, "require": { @@ -596,9 +596,9 @@ "homepage": "https://github.com/woocommerce/woocommerce-admin", "support": { "issues": "https://github.com/woocommerce/woocommerce-admin/issues", - "source": "https://github.com/woocommerce/woocommerce-admin/tree/v2.5.0" + "source": "https://github.com/woocommerce/woocommerce-admin/tree/v2.5.1" }, - "time": "2021-08-09T21:00:32+00:00" + "time": "2021-08-16T14:31:33+00:00" }, { "name": "woocommerce/woocommerce-blocks", diff --git a/includes/class-wc-cart-totals.php b/includes/class-wc-cart-totals.php index 54afe9827ca..87464c756a7 100644 --- a/includes/class-wc-cart-totals.php +++ b/includes/class-wc-cart-totals.php @@ -226,7 +226,7 @@ final class WC_Cart_Totals { $item->taxable = 'taxable' === $cart_item['data']->get_tax_status(); $item->price_includes_tax = wc_prices_include_tax(); $item->quantity = $cart_item['quantity']; - $item->price = wc_add_number_precision_deep( $cart_item['data']->get_price() * $cart_item['quantity'] ); + $item->price = wc_add_number_precision_deep( (float) $cart_item['data']->get_price() * (float) $cart_item['quantity'] ); $item->product = $cart_item['data']; $item->tax_rates = $this->get_item_tax_rates( $item ); $this->items[ $cart_item_key ] = $item; diff --git a/includes/class-wc-countries.php b/includes/class-wc-countries.php index 16929d1b600..fd11b798b12 100644 --- a/includes/class-wc-countries.php +++ b/includes/class-wc-countries.php @@ -809,6 +809,7 @@ class WC_Countries { 'AF' => array( 'state' => array( 'required' => false, + 'hidden' => true, ), ), 'AO' => array( @@ -826,6 +827,7 @@ class WC_Countries { ), 'state' => array( 'required' => false, + 'hidden' => true, ), ), 'AU' => array( @@ -845,6 +847,7 @@ class WC_Countries { ), 'state' => array( 'required' => false, + 'hidden' => true, ), ), 'BA' => array( @@ -871,7 +874,7 @@ class WC_Countries { ), 'state' => array( 'required' => false, - 'label' => __( 'Province', 'woocommerce' ), + 'hidden' => true, ), ), 'BH' => array( @@ -880,11 +883,13 @@ class WC_Countries { ), 'state' => array( 'required' => false, + 'hidden' => true, ), ), 'BI' => array( 'state' => array( 'required' => false, + 'hidden' => true, ), ), 'BO' => array( @@ -949,6 +954,7 @@ class WC_Countries { 'CZ' => array( 'state' => array( 'required' => false, + 'hidden' => true, ), ), 'DE' => array( @@ -975,6 +981,7 @@ class WC_Countries { ), 'state' => array( 'required' => false, + 'hidden' => true, ), ), 'FI' => array( @@ -983,6 +990,7 @@ class WC_Countries { ), 'state' => array( 'required' => false, + 'hidden' => true, ), ), 'FR' => array( @@ -991,6 +999,7 @@ class WC_Countries { ), 'state' => array( 'required' => false, + 'hidden' => true, ), ), 'GH' => array( @@ -1004,11 +1013,13 @@ class WC_Countries { 'GP' => array( 'state' => array( 'required' => false, + 'hidden' => true, ), ), 'GF' => array( 'state' => array( 'required' => false, + 'hidden' => true, ), ), 'GR' => array( @@ -1082,6 +1093,7 @@ class WC_Countries { ), 'state' => array( 'required' => false, + 'hidden' => true, ), ), 'IL' => array( @@ -1090,11 +1102,13 @@ class WC_Countries { ), 'state' => array( 'required' => false, + 'hidden' => true, ), ), 'IM' => array( 'state' => array( 'required' => false, + 'hidden' => true, ), ), 'IN' => array( @@ -1158,11 +1172,13 @@ class WC_Countries { 'KR' => array( 'state' => array( 'required' => false, + 'hidden' => true, ), ), 'KW' => array( 'state' => array( 'required' => false, + 'hidden' => true, ), ), 'LV' => array( @@ -1174,16 +1190,19 @@ class WC_Countries { 'LB' => array( 'state' => array( 'required' => false, + 'hidden' => true, ), ), 'MQ' => array( 'state' => array( 'required' => false, + 'hidden' => true, ), ), 'MT' => array( 'state' => array( 'required' => false, + 'hidden' => true, ), ), 'MZ' => array( @@ -1201,7 +1220,7 @@ class WC_Countries { ), 'state' => array( 'required' => false, - 'label' => __( 'Province', 'woocommerce' ), + 'hidden' => true, ), ), 'NG' => array( @@ -1229,6 +1248,7 @@ class WC_Countries { ), 'state' => array( 'required' => false, + 'hidden' => true, ), ), 'NP' => array( @@ -1245,6 +1265,7 @@ class WC_Countries { ), 'state' => array( 'required' => false, + 'hidden' => true, ), ), 'PR' => array( @@ -1259,11 +1280,13 @@ class WC_Countries { 'PT' => array( 'state' => array( 'required' => false, + 'hidden' => true, ), ), 'RE' => array( 'state' => array( 'required' => false, + 'hidden' => true, ), ), 'RO' => array( @@ -1287,6 +1310,7 @@ class WC_Countries { 'SG' => array( 'state' => array( 'required' => false, + 'hidden' => true, ), 'city' => array( 'required' => false, @@ -1298,6 +1322,7 @@ class WC_Countries { ), 'state' => array( 'required' => false, + 'hidden' => true, ), ), 'SI' => array( @@ -1306,6 +1331,7 @@ class WC_Countries { ), 'state' => array( 'required' => false, + 'hidden' => true, ), ), 'SR' => array( @@ -1334,11 +1360,13 @@ class WC_Countries { 'LK' => array( 'state' => array( 'required' => false, + 'hidden' => true, ), ), 'LU' => array( 'state' => array( 'required' => false, + 'hidden' => true, ), ), 'MD' => array( @@ -1427,6 +1455,7 @@ class WC_Countries { 'YT' => array( 'state' => array( 'required' => false, + 'hidden' => true, ), ), 'ZA' => array( diff --git a/includes/class-wc-discounts.php b/includes/class-wc-discounts.php index 522495fee8d..8b2c5b4aabc 100644 --- a/includes/class-wc-discounts.php +++ b/includes/class-wc-discounts.php @@ -84,7 +84,7 @@ class WC_Discounts { $item->object = $cart_item; $item->product = $cart_item['data']; $item->quantity = $cart_item['quantity']; - $item->price = wc_add_number_precision_deep( $item->product->get_price() * $item->quantity ); + $item->price = wc_add_number_precision_deep( (float) $item->product->get_price() * (float) $item->quantity ); $this->items[ $key ] = $item; } diff --git a/includes/class-wc-tracker.php b/includes/class-wc-tracker.php index e12803022aa..2878e2f791a 100644 --- a/includes/class-wc-tracker.php +++ b/includes/class-wc-tracker.php @@ -160,9 +160,6 @@ class WC_Tracker { // Template overrides. $data['template_overrides'] = self::get_all_template_overrides(); - // Template overrides. - $data['admin_user_agents'] = self::get_admin_user_agents(); - // Cart & checkout tech (blocks or shortcodes). $data['cart_checkout'] = self::get_cart_checkout_info(); @@ -619,6 +616,8 @@ class WC_Tracker { 'version' => WC()->version, 'currency' => get_woocommerce_currency(), 'base_location' => WC()->countries->get_base_country(), + 'base_state' => WC()->countries->get_base_state(), + 'base_postcode' => WC()->countries->get_base_postcode(), 'selling_locations' => WC()->countries->get_allowed_countries(), 'api_enabled' => get_option( 'woocommerce_api_enabled' ), 'weight_unit' => get_option( 'woocommerce_weight_unit' ), @@ -675,15 +674,6 @@ class WC_Tracker { return $override_data; } - /** - * When an admin user logs in, there user agent is tracked in user meta and collected here. - * - * @return array - */ - private static function get_admin_user_agents() { - return array_filter( (array) get_option( 'woocommerce_tracker_ua', array() ) ); - } - /** * Search a specific post for text content. * diff --git a/includes/data-stores/class-wc-product-data-store-cpt.php b/includes/data-stores/class-wc-product-data-store-cpt.php index 25ca18d6515..190749d7db3 100644 --- a/includes/data-stores/class-wc-product-data-store-cpt.php +++ b/includes/data-stores/class-wc-product-data-store-cpt.php @@ -1315,8 +1315,6 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da AND p.post_type = 'product' ", - 'orderby' => ' - ORDER BY RAND()', 'limits' => ' LIMIT ' . absint( $limit ) . ' ', diff --git a/tests/e2e/api/package-lock.json b/tests/e2e/api/package-lock.json index 579b4399c2f..6b0ed650a71 100644 --- a/tests/e2e/api/package-lock.json +++ b/tests/e2e/api/package-lock.json @@ -3532,9 +3532,9 @@ "dev": true }, "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, "performance-now": { diff --git a/tests/e2e/config/jest.setup.js b/tests/e2e/config/jest.setup.js index 28ae1fb2ab6..f219ddbb54d 100644 --- a/tests/e2e/config/jest.setup.js +++ b/tests/e2e/config/jest.setup.js @@ -8,6 +8,7 @@ import { const config = require('config'); const { HTTPClientFactory } = require('@woocommerce/api'); const { addConsoleSuppression, updateReadyPageStatus } = require( '@woocommerce/e2e-environment' ); +const { DEFAULT_TIMEOUT_OVERRIDE } = process.env; // @todo: remove this once https://github.com/woocommerce/woocommerce-admin/issues/6992 has been addressed addConsoleSuppression( 'woocommerce_shared_settings' ); @@ -38,6 +39,12 @@ async function trashExistingPosts() { // other posts/comments/etc. aren't dirtying tests and tests don't depend on // each other's side-effects. beforeAll(async () => { + + if ( DEFAULT_TIMEOUT_OVERRIDE ) { + page.setDefaultNavigationTimeout( DEFAULT_TIMEOUT_OVERRIDE ); + page.setDefaultTimeout( DEFAULT_TIMEOUT_OVERRIDE ); + } + // Update the ready page to prevent concurrent test runs await updateReadyPageStatus('draft'); diff --git a/tests/e2e/core-tests/specs/shopper/front-end-cart.test.js b/tests/e2e/core-tests/specs/shopper/front-end-cart.test.js index e9bc8c8445c..c11636fa68d 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-cart.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-cart.test.js @@ -1,9 +1,9 @@ -/* eslint-disable jest/no-export, jest/no-disabled-tests, jest/expect-expect */ /** * Internal dependencies */ const { shopper, + withRestApi, createSimpleProduct, uiUnblocked } = require( '@woocommerce/e2e-utils' ); @@ -26,6 +26,9 @@ const runCartPageTest = () => { describe('Cart page', () => { beforeAll(async () => { await createSimpleProduct(); + await withRestApi.resetSettingsGroupToDefault('general'); + await withRestApi.resetSettingsGroupToDefault('products'); + await withRestApi.resetSettingsGroupToDefault('tax'); }); it('should display no item in the cart', async () => { diff --git a/tests/e2e/core-tests/specs/shopper/front-end-checkout.test.js b/tests/e2e/core-tests/specs/shopper/front-end-checkout.test.js index 812d7385d25..fb73d94b565 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-checkout.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-checkout.test.js @@ -1,10 +1,10 @@ -/* eslint-disable jest/no-export, jest/no-disabled-tests, jest/expect-expect, jest/no-standalone-expect */ /** * Internal dependencies */ const { shopper, merchant, + withRestApi, createSimpleProduct, setCheckbox, settingsPageSaveChanges, @@ -28,6 +28,9 @@ const runCheckoutPageTest = () => { describe('Checkout page', () => { beforeAll(async () => { await createSimpleProduct(); + await withRestApi.resetSettingsGroupToDefault('general'); + await withRestApi.resetSettingsGroupToDefault('products'); + await withRestApi.resetSettingsGroupToDefault('tax'); // Set free shipping within California await merchant.login(); diff --git a/tests/e2e/env/CHANGELOG.md b/tests/e2e/env/CHANGELOG.md index e57468d8e24..69b47c445ad 100644 --- a/tests/e2e/env/CHANGELOG.md +++ b/tests/e2e/env/CHANGELOG.md @@ -1,11 +1,13 @@ # Unreleased -- `updateReadyPageStatus` utility to update the status of the ready page -- Added plugin upload functionality util that provides a method to pull a plugin zip from a remote location +- `updateReadyPageStatus` utility to update the status of the ready page. +- Added plugin upload functionality util that provides a method to pull a plugin zip from a remote location: - `getRemotePluginZip( fileUrl )` to get the remote zip. Returns the filepath of the zip location. - Added plugin zip utility functions: - `checkNestedZip( zipFilePath, savePath )` checks a plugin zip file for any nested zip files. If one is found, it is extracted. Returns the path where the zip file is located. - `downloadZip( fileUrl, downloadPath )` downloads a plugin zip file from a remote location to the provided path. +- Added `getLatestReleaseZipUrl( owner, repository, getPrerelease, perPage )` util function to get the latest release zip from a GitHub repository. +- Added `DEFAULT_TIMEOUT_OVERRIDE` that allows passing in a time in milliseconds to override the default Jest and Puppeteer timeouts. # 0.2.2 diff --git a/tests/e2e/env/README.md b/tests/e2e/env/README.md index c3b38dd3bd2..a9e2e1ece71 100644 --- a/tests/e2e/env/README.md +++ b/tests/e2e/env/README.md @@ -87,6 +87,21 @@ await takeScreenshotFor( 'name of current step' ); Screenshots will be saved to `tests/e2e/screenshots`. This folder is cleared at the beginning of each test run. +### Override default test timeout + +To override the default timeout for the tests, you can use the `DEFAULT_TIMEOUT_OVERRIDE` flag and pass in a maximum timeout in milliseconds. For example, you can pass it in when running the tests from the command line: + +```bash +DEFAULT_TIMEOUT_OVERRIDE=35000 npx wc-e2e test:e2e +``` + +This value will override the default Jest timeout as well as pass the timeout to the following Puppeteer methods: + +* page.setDefaultTimeout(); +* page.setDefaultNavigationTimeout(); + +For a list of the methods that the above timeout affects, please see the Puppeteer documentation for [`page.setDefaultTimeout()`](https://pptr.dev/#?product=Puppeteer&version=v10.2.0&show=api-pagesetdefaulttimeouttimeout) and [`page.setDefaultNavigationTimeout`](https://pptr.dev/#?product=Puppeteer&version=v10.2.0&show=api-pagesetdefaultnavigationtimeouttimeout) for more information. + ### Jest Puppeteer Config The test sequencer uses the following default Puppeteer configuration: @@ -196,6 +211,14 @@ The above method also makes use of the following utility methods which can also - `checkNestedZip( zipFilePath, savePath )` used to check a plugin zip file for any nested zip files. If one is found, it is extracted. Returns the path where the zip file is located. - `downloadZip( fileUrl, downloadPath )` can be used to directly download a plugin zip file from a remote location to the provided path. +### Get the latest released zip URL + +If you would like to get the latest release zip URL, which can be used in the methods mentioned above, you can use the following helper function to do so: + +`getLatestReleaseZipUrl( owner, repository, getPrerelease, perPage )` + +This will return a string with the latest release URL. Optionally, you can use the `getPrerelease` boolean flag, which defaults to false, on whether or not to get a prerelease instead. The `perPage` flag can be used to return more results when getting the list of releases. The default value is 3. + ## Additional information Refer to [`tests/e2e/core-tests`](https://github.com/woocommerce/woocommerce/tree/trunk/tests/e2e/core-tests) for some test examples, and [`tests/e2e`](https://github.com/woocommerce/woocommerce/tree/trunk/tests/e2e) for general information on e2e tests. diff --git a/tests/e2e/env/bin/e2e-test-integration.js b/tests/e2e/env/bin/e2e-test-integration.js index 46d457d974b..3cae8875e5e 100755 --- a/tests/e2e/env/bin/e2e-test-integration.js +++ b/tests/e2e/env/bin/e2e-test-integration.js @@ -5,7 +5,7 @@ const program = require( 'commander' ); const path = require( 'path' ); const fs = require( 'fs' ); const { getAppRoot } = require( '../utils' ); -const { WC_E2E_SCREENSHOTS, JEST_PUPPETEER_CONFIG } = process.env; +const { WC_E2E_SCREENSHOTS, JEST_PUPPETEER_CONFIG, DEFAULT_TIMEOUT_OVERRIDE } = process.env; program .usage( ' [options]' ) @@ -43,6 +43,10 @@ let testEnvVars = { jest_test_timeout: program.dev ? 120000 : 30000, }; +if ( DEFAULT_TIMEOUT_OVERRIDE ) { + testEnvVars.jest_test_timeout = DEFAULT_TIMEOUT_OVERRIDE; +} + if ( ! JEST_PUPPETEER_CONFIG ) { // Use local Puppeteer config if there is one. // Load test configuration file into an object. diff --git a/tests/e2e/env/utils/get-plugin-zip.js b/tests/e2e/env/utils/get-plugin-zip.js index edd9f404be9..856478719db 100644 --- a/tests/e2e/env/utils/get-plugin-zip.js +++ b/tests/e2e/env/utils/get-plugin-zip.js @@ -29,6 +29,52 @@ const getRemotePluginZip = async ( fileUrl ) => { return filePath; }; +/** + * Get the latest release zip for a plugin from a GiHub repository. + * + * @param {string} owner The owner of the plugin repository. + * @param {string} repository The repository name. + * @param {boolean} getPrerelease Flag on whether to get a prelease or not. + * @param {number} perPage Limit of entries returned from the latest releases list, defaults to 3. + * @returns {Promise}} Returns the URL for the release zip file. + */ +const getLatestReleaseZipUrl = async ( owner, repository, getPrerelease = false, perPage = 3 ) => { + let requesturl; + + if ( getPrerelease ) { + requesturl = `https://api.github.com/repos/${owner}/${repository}/releases?per_page=${perPage}`; + } else { + requesturl = `https://api.github.com/repos/${owner}/${repository}/releases/latest`; + } + + const options = { + url: requesturl, + method: 'GET', + json: true, + headers: {'user-agent': 'node.js'} + }; + + // Wrap in a promise to make the request async + return new Promise( function( resolve, reject ) { + request.get(options, function( err, resp, body ) { + if ( err ) { + reject( err ); + } else { + if ( getPrerelease ) { + // Loop until we find the first pre-release, then return it. + body.forEach(release => { + if ( release.prerelease ) { + resolve( release.zipball_url ); + } + }); + } else { + resolve( body.zipball_url ); + } + } + }) + }); +} + /** * Check the zip file for any nested zips. If one is found, extract it. * @@ -80,6 +126,7 @@ const downloadZip = async ( fileUrl, downloadPath ) => { module.exports = { getRemotePluginZip, + getLatestReleaseZipUrl, checkNestedZip, downloadZip, }; diff --git a/tests/e2e/env/utils/index.js b/tests/e2e/env/utils/index.js index 6df8984caf7..3a3e220c88c 100644 --- a/tests/e2e/env/utils/index.js +++ b/tests/e2e/env/utils/index.js @@ -1,7 +1,7 @@ const getAppRoot = require( './app-root' ); const { getAppName, getAppBase } = require( './app-name' ); const { getTestConfig, getAdminConfig } = require( './test-config' ); -const { getRemotePluginZip } = require('./get-plugin-zip'); +const { getRemotePluginZip, getLatestReleaseZipUrl } = require('./get-plugin-zip'); const takeScreenshotFor = require( './take-screenshot' ); const updateReadyPageStatus = require('./update-ready-page'); const consoleUtils = require( './filter-console' ); @@ -13,6 +13,7 @@ module.exports = { getTestConfig, getAdminConfig, getRemotePluginZip, + getLatestReleaseZipUrl, takeScreenshotFor, updateReadyPageStatus, ...consoleUtils, diff --git a/tests/e2e/specs/smoke-tests/update-woocommerce.js b/tests/e2e/specs/smoke-tests/update-woocommerce.js index 8b1d2380a15..1aef2383a4e 100644 --- a/tests/e2e/specs/smoke-tests/update-woocommerce.js +++ b/tests/e2e/specs/smoke-tests/update-woocommerce.js @@ -3,7 +3,7 @@ */ const { merchant, utils } = require( '@woocommerce/e2e-utils' ); -const { getRemotePluginZip } = require( '@woocommerce/e2e-environment' ); +const { getRemotePluginZip, getLatestReleaseZipUrl } = require( '@woocommerce/e2e-environment' ); /** * External dependencies @@ -13,16 +13,23 @@ const { beforeAll, } = require( '@jest/globals' ); -const { UPDATE_WC } = process.env; +const { UPDATE_WC, TEST_RELEASE } = process.env; -const nightlyZip = 'https://github.com/woocommerce/woocommerce/releases/download/nightly/woocommerce-trunk-nightly.zip'; +let zipUrl; const pluginName = 'WooCommerce'; let pluginPath; utils.describeIf( UPDATE_WC )( 'WooCommerce plugin can be uploaded and activated', () => { beforeAll( async () => { - pluginPath = await getRemotePluginZip( nightlyZip ); + + if ( TEST_RELEASE ) { + zipUrl = await getLatestReleaseZipUrl('woocommerce', 'woocommerce'); + } else { + zipUrl = 'https://github.com/woocommerce/woocommerce/releases/download/nightly/woocommerce-trunk-nightly.zip'; + } + + pluginPath = await getRemotePluginZip( zipUrl ); await merchant.login(); }); @@ -34,4 +41,9 @@ utils.describeIf( UPDATE_WC )( 'WooCommerce plugin can be uploaded and activated await merchant.uploadAndActivatePlugin( pluginPath, pluginName ); }); + it( 'can run the database update', async () => { + // Check for, and run, the database upgrade if needed + await merchant.runDatabaseUpdate(); + }); + }); diff --git a/tests/e2e/utils/CHANGELOG.md b/tests/e2e/utils/CHANGELOG.md index 05cb246aa6b..17a2b5d2d83 100644 --- a/tests/e2e/utils/CHANGELOG.md +++ b/tests/e2e/utils/CHANGELOG.md @@ -12,6 +12,7 @@ - Added `describeIf()` to conditionally run a test suite - Added `itIf()` to conditionally run a test case. - Added merchant workflows around plugins: `uploadAndActivatePlugin()`, `activatePlugin()`, `deactivatePlugin()`, `deletePlugin()` +- Added merchant workflows checking for a database update and performing the update if needed: `runDatabaseUpdate()` - Added `deleteAllOrders()` that goes through and deletes all orders - Added `deleteAllShippingClasses()` which permanently deletes all shipping classes using the API - Added `statuses` optional parameter to `deleteAllRepositoryObjects()` to delete on specific statuses diff --git a/tests/e2e/utils/README.md b/tests/e2e/utils/README.md index 4bc56544746..5d1cc3831d7 100644 --- a/tests/e2e/utils/README.md +++ b/tests/e2e/utils/README.md @@ -109,6 +109,7 @@ This package provides support for enabling retries in tests: | `updateWordPress` | | Install pending WordPress updates on Dashboard -> Updates| | `updatePlugins` | | Install all pending plugin updates on Dashboard -> Updates| | `updateThemes` | | Install all pending theme updates on Dashboard -> Updates| +| `runDatabaseUpdate` || Runs the database update if needed | ### Shopper `shopper` @@ -133,7 +134,7 @@ This package provides support for enabling retries in tests: | `removeFromCart` | `productTitle` | Remove a product from the cart on the cart page | | `setCartQuantity` | `productTitle, quantityValue` | Change the quantity of a product on the cart page | | `searchForProduct` | | Searching for a product name and landing on its detail page | -| `emptyCart` | | Removes any products and coupons that are in the cart | +| `emptyCart` | | Removes any products and coupons that are in the cart | ### REST API `withRestApi` diff --git a/tests/e2e/utils/package.json b/tests/e2e/utils/package.json index 89994c56a72..644c2129559 100644 --- a/tests/e2e/utils/package.json +++ b/tests/e2e/utils/package.json @@ -2,7 +2,7 @@ "name": "@woocommerce/e2e-utils", "version": "0.1.5", "description": "End-To-End (E2E) test utils for WooCommerce", - "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/tests/e2e-utils/README.md", + "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/tests/e2e/utils/README.md", "repository": { "type": "git", "url": "https://github.com/woocommerce/woocommerce.git" diff --git a/tests/e2e/utils/src/flows/merchant.js b/tests/e2e/utils/src/flows/merchant.js index c025d18ca29..eead6d9779d 100644 --- a/tests/e2e/utils/src/flows/merchant.js +++ b/tests/e2e/utils/src/flows/merchant.js @@ -373,6 +373,34 @@ const merchant = { // Wait for Ajax calls to finish await page.waitForResponse( response => response.status() === 200 ); + }, + + /** + * Runs the database update if needed. For example, after uploading the WooCommerce plugin or updating WooCommerce. + */ + runDatabaseUpdate: async () => { + if ( await page.$( '.updated.woocommerce-message.wc-connect' ) !== null ) { + await expect( page ).toMatchElement( 'a.wc-update-now', { text: 'Update WooCommerce Database' } ); + await expect( page ).toClick( 'a.wc-update-now' ); + await page.waitForNavigation( { waitUntil: 'networkidle0' } ); + await merchant.checkDatabaseUpdateComplete(); + } + }, + + /** + * Checks if the database update is complete, if not, refresh the page until it is. + */ + checkDatabaseUpdateComplete: async () => { + await page.reload( { waitUntil: [ 'networkidle0', 'domcontentloaded'] } ); + + const thanksButtonSelector = 'a.components-button.is-primary'; + + if ( await page.$( thanksButtonSelector ) !== null ) { + await expect( page ).toMatchElement( thanksButtonSelector, { text: 'Thanks!' } ); + await expect( page ).toClick( thanksButtonSelector ); + } else { + await merchant.checkDatabaseUpdateComplete(); + } }, };