diff --git a/.github/ISSUE_TEMPLATE/2-enhancement.yml b/.github/ISSUE_TEMPLATE/2-enhancement.yml index 3c7637cea8c..19383e15c27 100644 --- a/.github/ISSUE_TEMPLATE/2-enhancement.yml +++ b/.github/ISSUE_TEMPLATE/2-enhancement.yml @@ -1,5 +1,5 @@ name: ✨ Enhancement Request -description: If you have an idea to improve an existing feature in core or need something for development (such as a new hook) please let us know or submit a Pull Request! +description: If you have an idea to improve existing functionality in core or need something for development (such as a new hook) please let us know or submit a Pull Request! title: "[Enhancement]: " labels: ["type: enhancement", "status: awaiting triage"] body: diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index dae2134fb39..f8f1bcc6e24 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,8 @@ blank_issues_enabled: true contact_links: + - name: Feature Requests + url: https://woocommerce.com/feature-requests/woocommerce/ + about: If you have an idea for a new feature that you wished existed in WooCommerce, take a look at our Feature Requests and vote, or open a new Feature Request yourself! - name: 🔒 Security issue url: https://hackerone.com/automattic/ about: For security reasons, please report all security issues via HackerOne. If the issue is valid, a bug bounty will be paid out to you. Please disclose responsibly and not via GitHub (which allows for exploiting issues in the wild before the patch is released). diff --git a/.github/workflows/scripts/post-request-shared.php b/.github/workflows/scripts/post-request-shared.php index 67a6794feef..8d4bc442147 100644 --- a/.github/workflows/scripts/post-request-shared.php +++ b/.github/workflows/scripts/post-request-shared.php @@ -135,7 +135,7 @@ function get_latest_version_with_release() { } /** - * Function to retreive the sha1 reference for a given branch name. + * Function to retrieve the sha1 reference for a given branch name. * * @param string $branch The name of the branch. * @return string Returns the name of the branch, or a falsey value on error. diff --git a/changelog.txt b/changelog.txt index 6005a873509..c764986089e 100644 --- a/changelog.txt +++ b/changelog.txt @@ -54,7 +54,7 @@ * Fix - Prevent possible warning arising from use of woocommerce_wp_* family of functions. [#37026](https://github.com/woocommerce/woocommerce/pull/37026) * Fix - Record values for toggled checkboxes/features in settings [#37242](https://github.com/woocommerce/woocommerce/pull/37242) * Fix - Restore the sort order when orders are cached. [#36650](https://github.com/woocommerce/woocommerce/pull/36650) -* Fix - Treat order as seperate resource when validating for webhook since it's not necessarily a CPT anymore. [#36650](https://github.com/woocommerce/woocommerce/pull/36650) +* Fix - Treat order as separate resource when validating for webhook since it's not necessarily a CPT anymore. [#36650](https://github.com/woocommerce/woocommerce/pull/36650) * Fix - Update Customers report with latest user data after editing user. [#37237](https://github.com/woocommerce/woocommerce/pull/37237) * Add - Add "Create a new campaign" modal in Campaigns card in Multichannel Marketing page. [#37044](https://github.com/woocommerce/woocommerce/pull/37044) * Add - Add a cache for orders, to use when custom order tables are enabled [#35014](https://github.com/woocommerce/woocommerce/pull/35014) @@ -432,7 +432,7 @@ * Tweak - Resolve an error in the product tracking code by testing to see if the `post_type` query var is set before checking its value. [#34501](https://github.com/woocommerce/woocommerce/pull/34501) * Tweak - Simplify wording within the customer emails for on-hold orders. [#31886](https://github.com/woocommerce/woocommerce/pull/31886) * Tweak - WooCommerce has now been tested up to WordPress 6.1.x. [#35985](https://github.com/woocommerce/woocommerce/pull/35985) -* Performance - Split CALC_FOUND_ROW query into seperate count query for better performance. [#35468](https://github.com/woocommerce/woocommerce/pull/35468) +* Performance - Split CALC_FOUND_ROW query into separate count query for better performance. [#35468](https://github.com/woocommerce/woocommerce/pull/35468) * Enhancement - Add a bottom padding to the whole form [#35721](https://github.com/woocommerce/woocommerce/pull/35721) * Enhancement - Add a confirmation modal when the user tries to navigate away with unsaved changes [#35625](https://github.com/woocommerce/woocommerce/pull/35625) * Enhancement - Add a default placeholder title for newly added attributes and always show remove button for attributes [#35904](https://github.com/woocommerce/woocommerce/pull/35904) @@ -3371,7 +3371,7 @@ * Fix - Add protection around func_get_args_call for backwards compatibility. #28677 * Fix - Restore stock_status in REST API V3 response. #28731 * Fix - Revert some of the changes related to perf enhancements (27735) as it was breaking stock_status filter. #28745 -* Dev - Hook for intializing price slider in frontend. #28014 +* Dev - Hook for initializing price slider in frontend. #28014 * Dev - Add support for running a custom initialization script for tests. #28041 * Dev - Use the coenjacobs/mozart package to renamespace vendor packages. #28147 * Dev - Documentation for `wc_get_container`. #28269 @@ -5290,7 +5290,7 @@ * Fix - Fix edge case where `get_plugins` would not have the custom WooCommerce plugin headers if `get_plugins` was called early. #21669 * Fix - Prevent PHP warning when deprecated user meta starts with uppercase. #21943 * Fix - Fixed support for multiple query parameters translated to meta queries via REST API requests. #22108 -* Fix - Prevent PHP errors when trying to access non-existant report tabs. #22183 +* Fix - Prevent PHP errors when trying to access non-existent report tabs. #22183 * Fix - Filter by attributes dropdown placeholder text should not be wrapped in quotes. #22185 * Fix - Apply sale price until end of closing sale date. #22189 * Fix - Allow empty schema again when registering a custom field for the API. #22204 @@ -6612,7 +6612,7 @@ * Removed internal scroll from log viewer. * Add reply-to to admin emails. * Improved the zone setup flow. -* Made wc_get_wildcard_postcodes return the orignal postcode plus * since wildcards should match empty strings too. +* Made wc_get_wildcard_postcodes return the original postcode plus * since wildcards should match empty strings too. * Use all paid statuses in $customer->get_total_spent(). * Move location of billing email field to work with password managers. * Option to restrict selling locations by country. @@ -8122,7 +8122,7 @@ * Tweak - Flat rate shipping support for percentage factor of additional costs. * Tweak - local delivery _ pattern matching for postcodes. e.g. NG1___ would match NG1 1AA but not NG10 1AA. * Tweak - Improved layered nav OR count logic -* Tweak - Make shipping methods check if taxable, so when customer is VAT excempt taxes are not included in price. +* Tweak - Make shipping methods check if taxable, so when customer is VAT exempt taxes are not included in price. * Tweak - Coupon in admin bar new menu #3974 * Tweak - Shortcode tag filters + updated menu names to make white labelling easier. * Tweak - Removed placeholder polyfill. Use this plugin to replace functionality if required: https://wordpress.org/plugins/html5-placeholder-polyfill/ diff --git a/packages/js/components/changelog/dev-migrate-select-control-to-ts b/packages/js/components/changelog/dev-migrate-select-control-to-ts new file mode 100644 index 00000000000..475884bfaa6 --- /dev/null +++ b/packages/js/components/changelog/dev-migrate-select-control-to-ts @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Migrate select control component to TS diff --git a/packages/js/components/changelog/fix-37502 b/packages/js/components/changelog/fix-37502 new file mode 100644 index 00000000000..115429c4b01 --- /dev/null +++ b/packages/js/components/changelog/fix-37502 @@ -0,0 +1,4 @@ +Significance: patch +Type: tweak + +Correct spelling errors diff --git a/packages/js/components/changelog/update-37727 b/packages/js/components/changelog/update-37727 new file mode 100644 index 00000000000..9b6f5259944 --- /dev/null +++ b/packages/js/components/changelog/update-37727 @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Show comma separated list in ready only mode of select tree control diff --git a/packages/js/components/src/chart/README.md b/packages/js/components/src/chart/README.md index 28082ca574a..a8a4474a6cb 100644 --- a/packages/js/components/src/chart/README.md +++ b/packages/js/components/src/chart/README.md @@ -94,7 +94,7 @@ Name | Type | Default | Description `legendPosition` | One of: 'bottom', 'side', 'top', 'hidden' | `null` | Position the legend must be displayed in. If it's not defined, it's calculated depending on the viewport width and the mode `legendTotals` | Object | `null` | Values to overwrite the legend totals. If not defined, the sum of all line values will be used `screenReaderFormat` | One of type: string, func | `'%B %-d, %Y'` | A datetime formatting string or overriding function to format the screen reader labels -`showHeaderControls` | Boolean | `true` | Wether header UI controls must be displayed +`showHeaderControls` | Boolean | `true` | Whether header UI controls must be displayed `title` | String | `null` | A title describing this chart `tooltipLabelFormat` | One of type: string, func | `'%B %-d, %Y'` | A datetime formatting string or overriding function to format the tooltip label `tooltipValueFormat` | One of type: string, func | `','` | A number formatting string or function to format the value displayed in the tooltips diff --git a/packages/js/components/src/chart/index.js b/packages/js/components/src/chart/index.js index c5afe925f08..101753f08bd 100644 --- a/packages/js/components/src/chart/index.js +++ b/packages/js/components/src/chart/index.js @@ -577,7 +577,7 @@ Chart.propTypes = { PropTypes.func, ] ), /** - * Wether header UI controls must be displayed. + * Whether header UI controls must be displayed. */ showHeaderControls: PropTypes.bool, /** diff --git a/packages/js/components/src/experimental-select-control/combo-box.tsx b/packages/js/components/src/experimental-select-control/combo-box.tsx index a71a323ff94..9bbbea87778 100644 --- a/packages/js/components/src/experimental-select-control/combo-box.tsx +++ b/packages/js/components/src/experimental-select-control/combo-box.tsx @@ -75,8 +75,8 @@ export const ComboBox = ( { { + inputRef.current = node; if ( typeof inputProps.ref === 'function' ) { - inputRef.current = node; ( inputProps.ref as unknown as ( node: HTMLInputElement | null diff --git a/packages/js/components/src/experimental-select-control/select-control.scss b/packages/js/components/src/experimental-select-control/select-control.scss index cf71dadd8cd..0ec18150b59 100644 --- a/packages/js/components/src/experimental-select-control/select-control.scss +++ b/packages/js/components/src/experimental-select-control/select-control.scss @@ -12,6 +12,18 @@ border-color: var( --wp-admin-theme-color ); } + &.is-read-only.is-multiple.has-selected-items { + .woocommerce-experimental-select-control__combo-box-wrapper { + cursor: default; + } + + .woocommerce-experimental-select-control__input { + opacity: 0; + width: 0; + height: 0; + } + } + &__label { display: inline-block; margin-bottom: $gap-smaller; diff --git a/packages/js/components/src/experimental-select-control/select-control.tsx b/packages/js/components/src/experimental-select-control/select-control.tsx index 7957e980d55..ccab1447611 100644 --- a/packages/js/components/src/experimental-select-control/select-control.tsx +++ b/packages/js/components/src/experimental-select-control/select-control.tsx @@ -9,13 +9,14 @@ import { useMultipleSelection, GetInputPropsOptions, } from 'downshift'; +import { useInstanceId } from '@wordpress/compose'; import { useState, useEffect, createElement, Fragment, } from '@wordpress/element'; -import { search } from '@wordpress/icons'; +import { chevronDown } from '@wordpress/icons'; /** * Internal dependencies @@ -123,12 +124,16 @@ function SelectControl< ItemType = DefaultItemType >( { className, disabled, inputProps = {}, - suffix = , + suffix = , showToggleButton = false, __experimentalOpenMenuOnFocus = false, }: SelectControlProps< ItemType > ) { const [ isFocused, setIsFocused ] = useState( false ); const [ inputValue, setInputValue ] = useState( '' ); + const instanceId = useInstanceId( + SelectControl, + 'woocommerce-experimental-select-control' + ); let selectedItems = selected === null ? [] : selected; selectedItems = Array.isArray( selectedItems ) @@ -230,15 +235,24 @@ function SelectControl< ItemType = DefaultItemType >( { }, } ); + const isEventOutside = ( event: React.FocusEvent ) => { + return ! document + .querySelector( '.' + instanceId ) + ?.contains( event.relatedTarget ); + }; + const onRemoveItem = ( item: ItemType ) => { selectItem( null ); removeSelectedItem( item ); onRemove( item ); }; + const isReadOnly = ! isOpen && ! isFocused; + const selectedItemTags = multiple ? ( ( { className={ classnames( 'woocommerce-experimental-select-control', className, + instanceId, { + 'is-read-only': isReadOnly, 'is-focused': isFocused, + 'is-multiple': multiple, + 'has-selected-items': selectedItems.length, } ) } > @@ -282,7 +300,11 @@ function SelectControl< ItemType = DefaultItemType >( { openMenu(); } }, - onBlur: () => setIsFocused( false ), + onBlur: ( event: React.FocusEvent ) => { + if ( isEventOutside( event ) ) { + setIsFocused( false ); + } + }, placeholder, disabled, ...inputProps, diff --git a/packages/js/components/src/experimental-select-control/selected-items.scss b/packages/js/components/src/experimental-select-control/selected-items.scss index 8f7c223483a..e9565582172 100644 --- a/packages/js/components/src/experimental-select-control/selected-items.scss +++ b/packages/js/components/src/experimental-select-control/selected-items.scss @@ -1,3 +1,9 @@ +.woocommerce-experimental-select-control__selected-items.is-read-only { + font-size: 13px; + color: $gray-900; + font-family: var(--wp--preset--font-family--system-font); +} + .woocommerce-experimental-select-control__selected-item { margin-right: $gap-smallest; margin-top: 2px; diff --git a/packages/js/components/src/experimental-select-control/selected-items.tsx b/packages/js/components/src/experimental-select-control/selected-items.tsx index 65aab895f76..6f82039e640 100644 --- a/packages/js/components/src/experimental-select-control/selected-items.tsx +++ b/packages/js/components/src/experimental-select-control/selected-items.tsx @@ -1,7 +1,8 @@ /** * External dependencies */ -import { createElement, Fragment } from '@wordpress/element'; +import classnames from 'classnames'; +import { createElement } from '@wordpress/element'; /** * Internal dependencies @@ -10,6 +11,7 @@ import Tag from '../tag'; import { getItemLabelType, getItemValueType } from './types'; type SelectedItemsProps< ItemType > = { + isReadOnly: boolean; items: ItemType[]; getItemLabel: getItemLabelType< ItemType >; getItemValue: getItemValueType< ItemType >; @@ -22,14 +24,34 @@ type SelectedItemsProps< ItemType > = { }; export const SelectedItems = < ItemType, >( { + isReadOnly, items, getItemLabel, getItemValue, getSelectedItemProps, onRemove, }: SelectedItemsProps< ItemType > ) => { + const classes = classnames( + 'woocommerce-experimental-select-control__selected-items', + { + 'is-read-only': isReadOnly, + } + ); + + if ( isReadOnly ) { + return ( +
+ { items + .map( ( item ) => { + return getItemLabel( item ); + } ) + .join( ', ' ) } +
+ ); + } + return ( - <> +
{ items.map( ( item, index ) => { return ( // Disable reason: We prevent the default action to keep the input focused on click. @@ -42,6 +64,9 @@ export const SelectedItems = < ItemType, >( { selectedItem: item, index, } ) } + onMouseDown={ ( event ) => { + event.preventDefault(); + } } onClick={ ( event ) => { event.preventDefault(); } } @@ -56,6 +81,6 @@ export const SelectedItems = < ItemType, >( {
); } ) } - + ); }; diff --git a/packages/js/components/src/experimental-select-tree-control/select-tree-menu.tsx b/packages/js/components/src/experimental-select-tree-control/select-tree-menu.tsx index 294ceafef60..fe26480d94b 100644 --- a/packages/js/components/src/experimental-select-tree-control/select-tree-menu.tsx +++ b/packages/js/components/src/experimental-select-tree-control/select-tree-menu.tsx @@ -22,6 +22,7 @@ import { } from '../experimental-tree-control'; type MenuProps = { + isEventOutside: ( event: React.FocusEvent ) => boolean; isOpen: boolean; isLoading?: boolean; position?: Popover.Position; @@ -32,6 +33,7 @@ type MenuProps = { } & Omit< TreeControlProps, 'items' >; export const SelectTreeMenu = ( { + isEventOutside, isLoading, isOpen, className, @@ -103,8 +105,10 @@ export const SelectTreeMenu = ( { ) } position={ position } animate={ false } - onFocusOutside={ () => { - onClose(); + onFocusOutside={ ( event ) => { + if ( isEventOutside( event ) ) { + onClose(); + } } } > { isOpen && ( diff --git a/packages/js/components/src/experimental-select-tree-control/select-tree.tsx b/packages/js/components/src/experimental-select-tree-control/select-tree.tsx index 5c276342cc8..2fe7e5e4fad 100644 --- a/packages/js/components/src/experimental-select-tree-control/select-tree.tsx +++ b/packages/js/components/src/experimental-select-tree-control/select-tree.tsx @@ -2,9 +2,9 @@ /** * External dependencies */ -import { createElement, useState } from '@wordpress/element'; +import { chevronDown } from '@wordpress/icons'; import classNames from 'classnames'; -import { search } from '@wordpress/icons'; +import { createElement, useState } from '@wordpress/element'; import { useInstanceId } from '@wordpress/compose'; /** @@ -32,7 +32,7 @@ export const SelectTree = function SelectTree( { items, getSelectedItemProps, treeRef: ref, - suffix = , + suffix = , placeholder, isLoading, onInputChange, @@ -40,24 +40,37 @@ export const SelectTree = function SelectTree( { ...props }: SelectTreeProps ) { const linkedTree = useLinkedTree( items ); + const selectTreeInstanceId = useInstanceId( + SelectTree, + 'woocommerce-experimental-select-tree-control__dropdown' + ); const menuInstanceId = useInstanceId( SelectTree, 'woocommerce-select-tree-control__menu' ); + const isEventOutside = ( event: React.FocusEvent ) => { + return ! document + .querySelector( '.' + selectTreeInstanceId ) + ?.contains( event.relatedTarget ); + }; const [ isFocused, setIsFocused ] = useState( false ); const [ isOpen, setIsOpen ] = useState( false ); + const isReadOnly = ! isOpen && ! isFocused; return (
@@ -93,12 +106,7 @@ export const SelectTree = function SelectTree( { }, onBlur: ( event ) => { // if blurring to an element inside the dropdown, don't close it - if ( - isOpen && - ! document - .querySelector( '.' + menuInstanceId ) - ?.contains( event.relatedTarget ) - ) { + if ( isEventOutside( event ) ) { setIsOpen( false ); } setIsFocused( false ); @@ -126,13 +134,13 @@ export const SelectTree = function SelectTree( { suffix={ suffix } > item?.label || '' } getItemValue={ ( item ) => item?.value || '' } onRemove={ ( item ) => { if ( ! Array.isArray( item ) && props.onRemove ) { props.onRemove( item ); - setIsOpen( false ); } } } getSelectedItemProps={ () => ( {} ) } @@ -144,10 +152,13 @@ export const SelectTree = function SelectTree( { id={ `${ props.id }-menu` } className={ menuInstanceId.toString() } ref={ ref } + isEventOutside={ isEventOutside } isOpen={ isOpen } items={ linkedTree } shouldShowCreateButton={ shouldShowCreateButton } - onClose={ () => setIsOpen( false ) } + onClose={ () => { + setIsOpen( false ); + } } />
); diff --git a/packages/js/components/src/filter-picker/README.md b/packages/js/components/src/filter-picker/README.md index 6e29b59e69c..080d7435855 100644 --- a/packages/js/components/src/filter-picker/README.md +++ b/packages/js/components/src/filter-picker/README.md @@ -65,8 +65,8 @@ The `config` prop has the following structure: - `label`: String - A label above the filter selector. - `staticParams`: Array - Url parameters to persist when selecting a new filter. -- `param`: String - The url paramter this filter will modify. -- `defaultValue`: String - The default paramter value to use instead of 'all'. +- `param`: String - The url parameter this filter will modify. +- `defaultValue`: String - The default parameter value to use instead of 'all'. - `showFilters`: Function - Determine if the filter should be shown. Supply a function with the query object as an argument returning a boolean. - `filters`: Array - Array of filter objects. diff --git a/packages/js/components/src/filter-picker/index.js b/packages/js/components/src/filter-picker/index.js index 7fb1652c377..a88f5e91427 100644 --- a/packages/js/components/src/filter-picker/index.js +++ b/packages/js/components/src/filter-picker/index.js @@ -374,11 +374,11 @@ FilterPicker.propTypes = { */ staticParams: PropTypes.array.isRequired, /** - * The url paramter this filter will modify. + * The url parameter this filter will modify. */ param: PropTypes.string.isRequired, /** - * The default paramter value to use instead of 'all'. + * The default parameter value to use instead of 'all'. */ defaultValue: PropTypes.string, /** diff --git a/packages/js/components/src/rich-text-editor/editor-writing-flow.tsx b/packages/js/components/src/rich-text-editor/editor-writing-flow.tsx index 5ee9e57dbdf..2726dc0cee8 100644 --- a/packages/js/components/src/rich-text-editor/editor-writing-flow.tsx +++ b/packages/js/components/src/rich-text-editor/editor-writing-flow.tsx @@ -45,7 +45,7 @@ export const EditorWritingFlow = ( { }; } ); - // This is a workaround to prevent focusing the block on intialization. + // This is a workaround to prevent focusing the block on initialization. // Changing to a mode other than "edit" ensures that no initial position // is found and no element gets subsequently focused. // See https://github.com/WordPress/gutenberg/blob/411b6eee8376e31bf9db4c15c92a80524ae38e9b/packages/block-editor/src/components/block-list/use-block-props/use-focus-first-element.js#L42 diff --git a/packages/js/components/src/search-list-control/test/hierarchy.js b/packages/js/components/src/search-list-control/test/hierarchy.js index b5d53216619..1a04d6abc69 100644 --- a/packages/js/components/src/search-list-control/test/hierarchy.js +++ b/packages/js/components/src/search-list-control/test/hierarchy.js @@ -153,7 +153,7 @@ describe( 'buildTermsTree', () => { ] ); } ); - test( 'should return a tree of items, with orphan categories appended to the end, with children of thier own', () => { + test( 'should return a tree of items, with orphan categories appended to the end, with children of their own', () => { const filteredList = [ { id: 1, name: 'Apricots', parent: 0 }, { id: 3, name: 'Elderberry', parent: 2 }, diff --git a/packages/js/components/src/select-control/control.js b/packages/js/components/src/select-control/control.tsx similarity index 69% rename from packages/js/components/src/select-control/control.js rename to packages/js/components/src/select-control/control.tsx index 931a4e425a2..a1864674663 100644 --- a/packages/js/components/src/select-control/control.js +++ b/packages/js/components/src/select-control/control.tsx @@ -6,18 +6,149 @@ import { BACKSPACE, DOWN, UP } from '@wordpress/keycodes'; import { createElement, Component, createRef } from '@wordpress/element'; import { Icon, search } from '@wordpress/icons'; import classnames from 'classnames'; -import PropTypes from 'prop-types'; +import { isArray } from 'lodash'; +import { + RefObject, + ChangeEvent, + FocusEvent, + KeyboardEvent, + InputHTMLAttributes, +} from 'react'; /** * Internal dependencies */ import Tags from './tags'; +import { Selected, Option } from './types'; + +type Props = { + /** + * Bool to determine if tags should be rendered. + */ + hasTags?: boolean; + /** + * Help text to be appended beneath the input. + */ + help?: string | JSX.Element; + /** + * Render tags inside input, otherwise render below input. + */ + inlineTags?: boolean; + /** + * Allow the select options to be filtered by search input. + */ + isSearchable?: boolean; + /** + * ID of the main SelectControl instance. + */ + instanceId?: number; + /** + * A label to use for the main input. + */ + label?: string; + /** + * ID used for a11y in the listbox. + */ + listboxId?: string; + /** + * Function called when the input is blurred. + */ + onBlur?: () => void; + /** + * Function called when selected results change, passed result list. + */ + onChange: ( selected: Option[] ) => void; + /** + * Function called when input field is changed or focused. + */ + onSearch: ( query: string ) => void; + /** + * A placeholder for the search input. + */ + placeholder?: string; + /** + * Search query entered by user. + */ + query?: string | null; + /** + * An array of objects describing selected values. If the label of the selected + * value is omitted, the Tag of that value will not be rendered inside the + * search box. + */ + selected?: Selected; + /** + * Show all options on focusing, even if a query exists. + */ + showAllOnFocus?: boolean; + /** + * Control input autocomplete field, defaults: off. + */ + autoComplete?: string; + /** + * Function to execute when the control should be expanded or collapsed. + */ + setExpanded: ( expanded: boolean ) => void; + /** + * Function to execute when the search value changes. + */ + updateSearchOptions: ( query: string ) => void; + /** + * Function to execute when keyboard navigation should decrement the selected index. + */ + decrementSelectedIndex: () => void; + /** + * Function to execute when keyboard navigation should increment the selected index. + */ + incrementSelectedIndex: () => void; + /** + * Multi-select mode allows multiple options to be selected. + */ + multiple?: boolean; + /** + * Is the control currently focused. + */ + isFocused?: boolean; + /** + * ID for accessibility purposes. aria-activedescendant will be set to this value. + */ + activeId?: string; + /** + * Disable the control. + */ + disabled?: boolean; + /** + * Is the control currently expanded. This is for accessibility purposes. + */ + isExpanded?: boolean; + /** + * The type of input to use for the search field. + */ + searchInputType?: InputHTMLAttributes< HTMLInputElement >[ 'type' ]; + /** + * The aria label for the search input. + */ + ariaLabel?: string; + /** + * Class name to be added to the input. + */ + className?: string; + /** + * Show the clear button. + */ + showClearButton?: boolean; +}; + +type State = { + isActive: boolean; +}; /** * A search control to allow user input to filter the options. */ -class Control extends Component { - constructor( props ) { +class Control extends Component< Props, State > { + input: RefObject< HTMLInputElement >; + + constructor( props: Props ) { super( props ); this.state = { isActive: false, @@ -31,13 +162,13 @@ class Control extends Component { this.onKeyDown = this.onKeyDown.bind( this ); } - updateSearch( onSearch ) { - return ( event ) => { + updateSearch( onSearch: ( query: string ) => void ) { + return ( event: ChangeEvent< HTMLInputElement > ) => { onSearch( event.target.value ); }; } - onFocus( onSearch ) { + onFocus( onSearch: ( query: string ) => void ) { const { isSearchable, setExpanded, @@ -45,7 +176,7 @@ class Control extends Component { updateSearchOptions, } = this.props; - return ( event ) => { + return ( event: FocusEvent< HTMLInputElement > ) => { this.setState( { isActive: true } ); if ( isSearchable && showAllOnFocus ) { event.target.select(); @@ -68,7 +199,7 @@ class Control extends Component { this.setState( { isActive: false } ); } - onKeyDown( event ) { + onKeyDown( event: KeyboardEvent< HTMLInputElement > ) { const { decrementSelectedIndex, incrementSelectedIndex, @@ -78,7 +209,12 @@ class Control extends Component { setExpanded, } = this.props; - if ( BACKSPACE === event.keyCode && ! query && selected.length ) { + if ( + BACKSPACE === event.keyCode && + ! query && + isArray( selected ) && + selected.length + ) { onChange( [ ...selected.slice( 0, -1 ) ] ); } @@ -100,7 +236,7 @@ class Control extends Component { renderButton() { const { multiple, selected } = this.props; - if ( multiple || ! selected.length ) { + if ( multiple || ! isArray( selected ) || ! selected.length ) { return null; } @@ -151,7 +287,7 @@ class Control extends Component { aria-describedby={ hasTags && inlineTags ? `search-inline-input-${ instanceId }` - : null + : undefined } disabled={ disabled } aria-label={ this.props.ariaLabel ?? this.props.label } @@ -168,7 +304,8 @@ class Control extends Component { query, selected, } = this.props; - const selectedValue = selected.length ? selected[ 0 ].label : ''; + const selectedValue = + isArray( selected ) && selected.length ? selected[ 0 ].label : ''; // Show the selected value for simple select dropdowns. if ( ! multiple && ! isFocused && ! inlineTags ) { @@ -194,6 +331,8 @@ class Control extends Component { isSearchable, label, query, + onChange, + showClearButton, } = this.props; const { isActive } = this.state; @@ -213,7 +352,7 @@ class Control extends Component { empty: ! query || query.length === 0, 'is-active': isActive, 'has-tags': inlineTags && hasTags, - 'with-value': this.getInputValue().length, + 'with-value': this.getInputValue()?.length, 'has-error': !! help, 'is-disabled': disabled, } @@ -221,8 +360,11 @@ class Control extends Component { onClick={ ( event ) => { // Don't focus the input if the click event is from the error message. if ( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - event.target.className is not in the type definition. event.target.className !== - 'components-base-control__help' + 'components-base-control__help' && + this.input.current ) { this.input.current.focus(); } @@ -234,7 +376,13 @@ class Control extends Component { icon={ search } /> ) } - { inlineTags && } + { inlineTags && ( + + ) }
{ !! label && ( @@ -272,75 +420,4 @@ class Control extends Component { } } -Control.propTypes = { - /** - * Bool to determine if tags should be rendered. - */ - hasTags: PropTypes.bool, - /** - * Help text to be appended beneath the input. - */ - help: PropTypes.oneOfType( [ PropTypes.string, PropTypes.node ] ), - /** - * Render tags inside input, otherwise render below input. - */ - inlineTags: PropTypes.bool, - /** - * Allow the select options to be filtered by search input. - */ - isSearchable: PropTypes.bool, - /** - * ID of the main SelectControl instance. - */ - instanceId: PropTypes.number, - /** - * A label to use for the main input. - */ - label: PropTypes.string, - /** - * ID used for a11y in the listbox. - */ - listboxId: PropTypes.string, - /** - * Function called when the input is blurred. - */ - onBlur: PropTypes.func, - /** - * Function called when selected results change, passed result list. - */ - onChange: PropTypes.func, - /** - * Function called when input field is changed or focused. - */ - onSearch: PropTypes.func, - /** - * A placeholder for the search input. - */ - placeholder: PropTypes.string, - /** - * Search query entered by user. - */ - query: PropTypes.string, - /** - * An array of objects describing selected values. If the label of the selected - * value is omitted, the Tag of that value will not be rendered inside the - * search box. - */ - selected: PropTypes.arrayOf( - PropTypes.shape( { - key: PropTypes.oneOfType( [ PropTypes.number, PropTypes.string ] ) - .isRequired, - label: PropTypes.string, - } ) - ), - /** - * Show all options on focusing, even if a query exists. - */ - showAllOnFocus: PropTypes.bool, - /** - * Control input autocomplete field, defaults: off. - */ - autoComplete: PropTypes.string, -}; - export default Control; diff --git a/packages/js/components/src/select-control/index.js b/packages/js/components/src/select-control/index.tsx similarity index 69% rename from packages/js/components/src/select-control/index.js rename to packages/js/components/src/select-control/index.tsx index 2432af33a9a..c3110eaa272 100644 --- a/packages/js/components/src/select-control/index.js +++ b/packages/js/components/src/select-control/index.tsx @@ -4,26 +4,205 @@ import { __, _n, sprintf } from '@wordpress/i18n'; import classnames from 'classnames'; import { Component, createElement } from '@wordpress/element'; -import { debounce, escapeRegExp, identity, noop } from 'lodash'; -import PropTypes from 'prop-types'; +import { + debounce, + escapeRegExp, + identity, + isArray, + isNumber, + noop, +} from 'lodash'; import { withFocusOutside, withSpokenMessages } from '@wordpress/components'; import { withInstanceId, compose } from '@wordpress/compose'; +import { ChangeEvent, InputHTMLAttributes } from 'react'; /** * Internal dependencies */ +import { Option, Selected } from './types'; import List from './list'; import Tags from './tags'; import Control from './control'; -const initialState = { isExpanded: false, isFocused: false, query: '' }; +type Props = { + /** + * Name to use for the autofill field, not used if no string is passed. + */ + autofill?: string; + /** + * A renderable component (or string) which will be displayed before the `Control` of this component. + */ + children?: React.ReactNode; + /** + * Class name applied to parent div. + */ + className?: string; + /** + * Class name applied to control wrapper. + */ + controlClassName?: string; + /** + * Allow the select options to be disabled. + */ + disabled?: boolean; + /** + * Exclude already selected options from the options list. + */ + excludeSelectedOptions?: boolean; + /** + * Add or remove items to the list of options after filtering, + * passed the array of filtered options and should return an array of options. + */ + onFilter?: ( + options: Array< Option >, + query: string | null + ) => Array< Option >; + /** + * Function to add regex expression to the filter the results, passed the search query. + */ + getSearchExpression?: ( query: string ) => RegExp; + /** + * Help text to be appended beneath the input. + */ + help?: string | JSX.Element; + /** + * Render tags inside input, otherwise render below input. + */ + inlineTags?: boolean; + /** + * Allow the select options to be filtered by search input. + */ + isSearchable?: boolean; + /** + * A label to use for the main input. + */ + label?: string; + /** + * Function called when selected results change, passed result list. + */ + onChange?: ( selected: string | Option[], query?: string | null ) => void; + /** + * Function run after search query is updated, passed previousOptions and query, + * should return a promise with an array of updated options. + */ + onSearch?: ( + previousOptions: Array< Option >, + query: string | null + ) => Promise< Array< Option > >; + /** + * An array of objects for the options list. The option along with its key, label and + * value will be returned in the onChange event. + */ + options: Option[]; + /** + * A placeholder for the search input. + */ + placeholder?: string; + /** + * Time in milliseconds to debounce the search function after typing. + */ + searchDebounceTime?: number; + /** + * An array of objects describing selected values or optionally a string for a single value. + * If the label of the selected value is omitted, the Tag of that value will not + * be rendered inside the search box. + */ + selected?: Selected; + /** + * A limit for the number of results shown in the options menu. Set to 0 for no limit. + */ + maxResults?: number; + /** + * Allow multiple option selections. + */ + multiple?: boolean; + /** + * Render a 'Clear' button next to the input box to remove its contents. + */ + showClearButton?: boolean; + /** + * The input type for the search box control. + */ + searchInputType?: InputHTMLAttributes< HTMLInputElement >[ 'type' ]; + /** + * Only show list options after typing a search query. + */ + hideBeforeSearch?: boolean; + /** + * Show all options on focusing, even if a query exists. + */ + showAllOnFocus?: boolean; + /** + * Render results list positioned statically instead of absolutely. + */ + staticList?: boolean; + /** + * autocomplete prop for the Control input field. + */ + autoComplete?: string; + /** + * Instance ID for the component. + */ + instanceId?: number; + /** + * From withSpokenMessages + */ + debouncedSpeak?: ( message: string, assertive?: string ) => void; + /** + * aria-label for the search input. + */ + ariaLabel?: string; + /** + * On Blur callback. + */ + onBlur?: () => void; +}; + +type State = { + isExpanded: boolean; + isFocused: boolean; + query: string | null; + searchOptions: Option[]; + selectedIndex?: number | null; +}; + +const initialState: State = { + isExpanded: false, + isFocused: false, + query: '', + searchOptions: [], +}; /** * A search box which filters options while typing, * allowing a user to select from an option from a filtered list. */ -export class SelectControl extends Component { - constructor( props ) { +export class SelectControl extends Component< Props, State > { + static defaultProps: Partial< Props > = { + excludeSelectedOptions: true, + getSearchExpression: identity, + inlineTags: false, + isSearchable: false, + onChange: noop, + onFilter: identity, + onSearch: ( options: Option[] ) => Promise.resolve( options ), + maxResults: 0, + multiple: false, + searchDebounceTime: 0, + searchInputType: 'search', + selected: [], + showAllOnFocus: false, + showClearButton: false, + hideBeforeSearch: false, + staticList: false, + autoComplete: 'off', + }; + + node: HTMLDivElement | null = null; + activePromise: Promise< void | Option[] > | null = null; + cacheSearchOptions: Option[] = []; + + constructor( props: Props ) { super( props ); const { selected, options, excludeSelectedOptions } = props; @@ -50,7 +229,7 @@ export class SelectControl extends Component { this.setNewValue = this.setNewValue.bind( this ); } - bindNode( node ) { + bindNode( node: HTMLDivElement ) { this.node = node; } @@ -58,7 +237,12 @@ export class SelectControl extends Component { const { multiple, excludeSelectedOptions } = this.props; const newState = { ...initialState }; // Reset selectedIndex if single selection. - if ( ! multiple && selected.length && selected[ 0 ].key ) { + if ( + ! multiple && + isArray( selected ) && + selected.length && + selected[ 0 ].key + ) { newState.selectedIndex = ! excludeSelectedOptions ? this.props.options.findIndex( ( i ) => i.key === selected[ 0 ].key @@ -101,9 +285,12 @@ export class SelectControl extends Component { return selectedOption ? [ selectedOption ] : []; } - selectOption( option ) { + selectOption( option: Option ) { const { multiple, selected } = this.props; - const newSelected = multiple ? [ ...selected, option ] : [ option ]; + const newSelected = + multiple && isArray( selected ) + ? [ ...selected, option ] + : [ option ]; this.reset( newSelected ); @@ -129,25 +316,24 @@ export class SelectControl extends Component { } ); } - setNewValue( newValue ) { + setNewValue( newValue: Option[] ) { const { onChange, selected, multiple } = this.props; const { query } = this.state; // Trigger a change if the selected value is different and pass back // an array or string depending on the original value. if ( multiple || Array.isArray( selected ) ) { - onChange( newValue, query ); + onChange!( newValue, query ); } else { - onChange( newValue.length > 0 ? newValue[ 0 ].key : '', query ); + onChange!( newValue.length > 0 ? newValue[ 0 ].key : '', query ); } } decrementSelectedIndex() { const { selectedIndex } = this.state; const options = this.getOptions(); - const nextSelectedIndex = - selectedIndex !== null - ? ( selectedIndex === 0 ? options.length : selectedIndex ) - 1 - : options.length - 1; + const nextSelectedIndex = isNumber( selectedIndex ) + ? ( selectedIndex === 0 ? options.length : selectedIndex ) - 1 + : options.length - 1; this.setState( { selectedIndex: nextSelectedIndex } ); } @@ -155,13 +341,14 @@ export class SelectControl extends Component { incrementSelectedIndex() { const { selectedIndex } = this.state; const options = this.getOptions(); - const nextSelectedIndex = - selectedIndex !== null ? ( selectedIndex + 1 ) % options.length : 0; + const nextSelectedIndex = isNumber( selectedIndex ) + ? ( selectedIndex + 1 ) % options.length + : 0; this.setState( { selectedIndex: nextSelectedIndex } ); } - announce( searchOptions ) { + announce( searchOptions: Option[] ) { const { debouncedSpeak } = this.props; if ( ! debouncedSpeak ) { return; @@ -169,6 +356,7 @@ export class SelectControl extends Component { if ( !! searchOptions.length ) { debouncedSpeak( sprintf( + // translators: %d: number of results. _n( '%d result found, use up and down arrow keys to navigate.', '%d results found, use up and down arrow keys to navigate.', @@ -187,23 +375,26 @@ export class SelectControl extends Component { getOptions() { const { isSearchable, options, excludeSelectedOptions } = this.props; const { searchOptions } = this.state; - const selectedKeys = this.getSelected().map( ( option ) => option.key ); + const selected = this.getSelected(); + const selectedKeys = isArray( selected ) + ? selected.map( ( option ) => option.key ) + : []; const shownOptions = isSearchable ? searchOptions : options; if ( excludeSelectedOptions ) { - return shownOptions.filter( + return shownOptions?.filter( ( option ) => ! selectedKeys.includes( option.key ) ); } return shownOptions; } - getOptionsByQuery( options, query ) { + getOptionsByQuery( options: Option[], query: string | null ) { const { getSearchExpression, maxResults, onFilter } = this.props; const filtered = []; // Create a regular expression to filter the options. - const expression = getSearchExpression( + const expression = getSearchExpression!( escapeRegExp( query ? query.trim() : '' ) ); const search = expression ? new RegExp( expression, 'i' ) : /^$/; @@ -232,14 +423,14 @@ export class SelectControl extends Component { } } - return onFilter( filtered, query ); + return onFilter!( filtered, query ); } - setExpanded( value ) { + setExpanded( value: boolean ) { this.setState( { isExpanded: value } ); } - search( query ) { + search( query: string | null ) { const cacheSearchOptions = this.cacheSearchOptions || []; const searchOptions = query !== null && ! query.length && ! this.props.hideBeforeSearch @@ -252,11 +443,13 @@ export class SelectControl extends Component { isFocused: true, searchOptions, selectedIndex: - query?.length > 0 ? null : this.state.selectedIndex, // Only reset selectedIndex if we're actually searching. + query && query?.length > 0 + ? null + : this.state.selectedIndex, // Only reset selectedIndex if we're actually searching. }, () => { this.setState( { - isExpanded: Boolean( this.getOptions().length ), + isExpanded: Boolean( this.getOptions()?.length ), } ); } ); @@ -264,11 +457,11 @@ export class SelectControl extends Component { this.updateSearchOptions( query ); } - updateSearchOptions( query ) { + updateSearchOptions( query: string | null ) { const { hideBeforeSearch, options, onSearch } = this.props; const promise = ( this.activePromise = Promise.resolve( - onSearch( options, query ) + onSearch!( options, query ) ).then( ( promiseOptions ) => { if ( promise !== this.activePromise ) { // Another promise has become active since this one was asked to resolve, so do nothing, @@ -288,7 +481,9 @@ export class SelectControl extends Component { { searchOptions, selectedIndex: - query?.length > 0 ? null : this.state.selectedIndex, // Only reset selectedIndex if we're actually searching. + query && query?.length > 0 + ? null + : this.state.selectedIndex, // Only reset selectedIndex if we're actually searching. }, () => { this.setState( { @@ -300,7 +495,7 @@ export class SelectControl extends Component { } ) ); } - onAutofillChange( event ) { + onAutofillChange( event: ChangeEvent< HTMLInputElement > ) { const { options } = this.props; const searchOptions = this.getOptionsByQuery( options, @@ -327,13 +522,14 @@ export class SelectControl extends Component { const { isExpanded, isFocused, selectedIndex } = this.state; const hasMultiple = this.hasMultiple(); - const { key: selectedKey = '' } = options[ selectedIndex ] || {}; + const { key: selectedKey = '' } = + ( isNumber( selectedIndex ) && options[ selectedIndex ] ) || {}; const listboxId = isExpanded ? `woocommerce-select-control__listbox-${ instanceId }` - : null; + : undefined; const activeId = isExpanded ? `woocommerce-select-control__option-${ instanceId }-${ selectedKey }` - : null; + : undefined; return (
) } { children } { ! inlineTags && hasMultiple && ( - + ) } { isExpanded && ( Promise.resolve( options ), - maxResults: 0, - multiple: false, - searchDebounceTime: 0, - searchInputType: 'search', - selected: [], - showAllOnFocus: false, - showClearButton: false, - hideBeforeSearch: false, - staticList: false, - autoComplete: 'off', -}; - export default compose( withSpokenMessages, withInstanceId, diff --git a/packages/js/components/src/select-control/list.js b/packages/js/components/src/select-control/list.tsx similarity index 68% rename from packages/js/components/src/select-control/list.js rename to packages/js/components/src/select-control/list.tsx index 1ebaf9f829a..eabb1985b40 100644 --- a/packages/js/components/src/select-control/list.js +++ b/packages/js/components/src/select-control/list.tsx @@ -2,18 +2,73 @@ * External dependencies */ import { Button } from '@wordpress/components'; +import { RefObject } from 'react'; import classnames from 'classnames'; import { createElement, Component, createRef } from '@wordpress/element'; -import { isEqual } from 'lodash'; +import { isEqual, isNumber } from 'lodash'; import { ENTER, ESCAPE, UP, DOWN, LEFT, RIGHT, TAB } from '@wordpress/keycodes'; -import PropTypes from 'prop-types'; + +/** + * Internal dependencies + */ +import { Option } from './types'; + +type Props = { + /** + * ID of the main SelectControl instance. + */ + listboxId?: string; + /** + * ID used for a11y in the listbox. + */ + instanceId: number; + /** + * Parent node to bind keyboard events to. + */ + node: HTMLElement | null; + /** + * Function to execute when an option is selected. + */ + onSelect: ( option: Option ) => void; + /** + * Array of options to display. + */ + options: Array< Option >; + /** + * Integer for the currently selected item. + */ + selectedIndex: number | null | undefined; + /** + * Bool to determine if the list should be positioned absolutely or staticly. + */ + staticList: boolean; + /** + * Function to execute when keyboard navigation should decrement the selected index. + */ + decrementSelectedIndex: () => void; + /** + * Function to execute when keyboard navigation should increment the selected index. + */ + incrementSelectedIndex: () => void; + /** + * Function to execute when the search value changes. + */ + onSearch: ( option: string | null ) => void; + /** + * Function to execute when the list should be expanded or collapsed. + */ + setExpanded: ( expanded: boolean ) => void; +}; /** * A list box that displays filtered options after search. */ -class List extends Component { - constructor() { - super( ...arguments ); +class List extends Component< Props > { + optionRefs: { [ key: number ]: RefObject< HTMLButtonElement > }; + listbox: RefObject< HTMLDivElement >; + + constructor( props: Props ) { + super( props ); this.handleKeyDown = this.handleKeyDown.bind( this ); this.select = this.select.bind( this ); @@ -21,7 +76,7 @@ class List extends Component { this.listbox = createRef(); } - componentDidUpdate( prevProps ) { + componentDidUpdate( prevProps: Props ) { const { options, selectedIndex } = this.props; // Remove old option refs to avoid memory leaks. @@ -29,12 +84,15 @@ class List extends Component { this.optionRefs = {}; } - if ( selectedIndex !== prevProps.selectedIndex ) { + if ( + selectedIndex !== prevProps.selectedIndex && + isNumber( selectedIndex ) + ) { this.scrollToOption( selectedIndex ); } } - getOptionRef( index ) { + getOptionRef( index: number ) { if ( ! this.optionRefs.hasOwnProperty( index ) ) { this.optionRefs[ index ] = createRef(); } @@ -42,7 +100,7 @@ class List extends Component { return this.optionRefs[ index ]; } - select( option ) { + select( option: Option ) { const { onSelect } = this.props; if ( option.isDisabled ) { @@ -52,9 +110,13 @@ class List extends Component { onSelect( option ); } - scrollToOption( index ) { + scrollToOption( index: number ) { const listbox = this.listbox.current; + if ( ! listbox ) { + return; + } + if ( listbox.scrollHeight <= listbox.clientHeight ) { return; } @@ -64,6 +126,12 @@ class List extends Component { } const option = this.optionRefs[ index ].current; + if ( ! option ) { + // eslint-disable-next-line no-console + console.warn( 'Option not found, index:', index ); + return; + } + const scrollBottom = listbox.clientHeight + listbox.scrollTop; const elementBottom = option.offsetTop + option.offsetHeight; if ( elementBottom > scrollBottom ) { @@ -73,7 +141,7 @@ class List extends Component { } } - handleKeyDown( event ) { + handleKeyDown( event: KeyboardEvent ) { const { decrementSelectedIndex, incrementSelectedIndex, @@ -100,7 +168,7 @@ class List extends Component { break; case ENTER: - if ( options[ selectedIndex ] ) { + if ( isNumber( selectedIndex ) && options[ selectedIndex ] ) { this.select( options[ selectedIndex ] ); } event.preventDefault(); @@ -118,7 +186,7 @@ class List extends Component { return; case TAB: - if ( options[ selectedIndex ] ) { + if ( isNumber( selectedIndex ) && options[ selectedIndex ] ) { this.select( options[ selectedIndex ] ); } setExpanded( false ); @@ -128,8 +196,14 @@ class List extends Component { } } - toggleKeyEvents( isListening ) { + toggleKeyEvents( isListening: boolean ) { const { node } = this.props; + if ( ! node ) { + // eslint-disable-next-line no-console + console.warn( 'No node to bind events to.' ); + return; + } + // This exists because we must capture ENTER key presses before RichText. // It seems that react fires the simulated capturing events after the // native browser event has already bubbled so we can't stopPropagation @@ -138,12 +212,16 @@ class List extends Component { const handler = isListening ? 'addEventListener' : 'removeEventListener'; - node[ handler ]( 'keydown', this.handleKeyDown, true ); + node[ handler ]( + 'keydown', + this.handleKeyDown as ( e: Event ) => void, + true + ); } componentDidMount() { const { selectedIndex } = this.props; - if ( selectedIndex > -1 ) { + if ( isNumber( selectedIndex ) && selectedIndex > -1 ) { this.scrollToOption( selectedIndex ); } this.toggleKeyEvents( true ); @@ -169,7 +247,7 @@ class List extends Component { id={ listboxId } role="listbox" className={ listboxClasses } - tabIndex="-1" + tabIndex={ -1 } > { options.map( ( option, index ) => ( @@ -196,50 +274,4 @@ class List extends Component { } } -List.propTypes = { - /** - * ID of the main SelectControl instance. - */ - instanceId: PropTypes.number, - /** - * ID used for a11y in the listbox. - */ - listboxId: PropTypes.string, - /** - * Parent node to bind keyboard events to. - */ - // eslint-disable-next-line no-undef - node: PropTypes.instanceOf( Element ).isRequired, - /** - * Function to execute when an option is selected. - */ - onSelect: PropTypes.func, - /** - * Array of options to display. - */ - options: PropTypes.arrayOf( - PropTypes.shape( { - isDisabled: PropTypes.bool, - key: PropTypes.oneOfType( [ PropTypes.number, PropTypes.string ] ) - .isRequired, - keywords: PropTypes.arrayOf( - PropTypes.oneOfType( [ PropTypes.string, PropTypes.number ] ) - ), - label: PropTypes.oneOfType( [ - PropTypes.string, - PropTypes.object, - ] ), - value: PropTypes.any, - } ) - ).isRequired, - /** - * Integer for the currently selected item. - */ - selectedIndex: PropTypes.number, - /** - * Bool to determine if the list should be positioned absolutely or staticly. - */ - staticList: PropTypes.bool, -}; - export default List; diff --git a/packages/js/components/src/select-control/stories/index.js b/packages/js/components/src/select-control/stories/index.tsx similarity index 95% rename from packages/js/components/src/select-control/stories/index.js rename to packages/js/components/src/select-control/stories/index.tsx index 9e0d5b0107f..292fb08f5a1 100644 --- a/packages/js/components/src/select-control/stories/index.js +++ b/packages/js/components/src/select-control/stories/index.tsx @@ -1,8 +1,12 @@ /** * External dependencies */ -import { SelectControl } from '@woocommerce/components'; -import { useState } from '@wordpress/element'; +import React from 'react'; +import { createElement, useState } from '@wordpress/element'; +/** + * Internal dependencies + */ +import SelectControl from '../'; const options = [ { diff --git a/packages/js/components/src/select-control/tags.js b/packages/js/components/src/select-control/tags.tsx similarity index 80% rename from packages/js/components/src/select-control/tags.js rename to packages/js/components/src/select-control/tags.tsx index 296e27bd4b2..e1d33a5b247 100644 --- a/packages/js/components/src/select-control/tags.js +++ b/packages/js/components/src/select-control/tags.tsx @@ -5,19 +5,36 @@ import { __, sprintf } from '@wordpress/i18n'; import { Button } from '@wordpress/components'; import { Icon, cancelCircleFilled } from '@wordpress/icons'; import { createElement, Component, Fragment } from '@wordpress/element'; -import { findIndex } from 'lodash'; -import PropTypes from 'prop-types'; +import { findIndex, isArray } from 'lodash'; /** * Internal dependencies */ import Tag from '../tag'; +import { Option, Selected } from './types'; + +type Props = { + /** + * Function called when selected results change, passed result list. + */ + onChange: ( selected: Option[] ) => void; + /** + * An array of objects describing selected values. If the label of the selected + * value is omitted, the Tag of that value will not be rendered inside the + * search box. + */ + selected?: Selected; + /** + * Render a 'Clear' button next to the input box to remove its contents. + */ + showClearButton?: boolean; +}; /** * A list of tags to display selected items. */ -class Tags extends Component { - constructor( props ) { +class Tags extends Component< Props > { + constructor( props: Props ) { super( props ); this.removeAll = this.removeAll.bind( this ); this.removeResult = this.removeResult.bind( this ); @@ -28,9 +45,13 @@ class Tags extends Component { onChange( [] ); } - removeResult( key ) { + removeResult( key: string | undefined ) { return () => { const { selected, onChange } = this.props; + if ( ! isArray( selected ) ) { + return; + } + const i = findIndex( selected, { key } ); onChange( [ ...selected.slice( 0, i ), @@ -41,7 +62,7 @@ class Tags extends Component { render() { const { selected, showClearButton } = this.props; - if ( ! selected.length ) { + if ( ! isArray( selected ) || ! selected.length ) { return null; } @@ -63,6 +84,7 @@ class Tags extends Component { key={ item.key } id={ item.key } label={ item.label } + // @ts-expect-error key is a string or undefined here remove={ this.removeResult } screenReaderLabel={ screenReaderLabel } /> @@ -89,31 +111,4 @@ class Tags extends Component { } } -Tags.propTypes = { - /** - * Function called when selected results change, passed result list. - */ - onChange: PropTypes.func, - /** - * Function to execute when an option is selected. - */ - onSelect: PropTypes.func, - /** - * An array of objects describing selected values. If the label of the selected - * value is omitted, the Tag of that value will not be rendered inside the - * search box. - */ - selected: PropTypes.arrayOf( - PropTypes.shape( { - key: PropTypes.oneOfType( [ PropTypes.number, PropTypes.string ] ) - .isRequired, - label: PropTypes.string, - } ) - ), - /** - * Render a 'Clear' button next to the input box to remove its contents. - */ - showClearButton: PropTypes.bool, -}; - export default Tags; diff --git a/packages/js/components/src/select-control/test/index.js b/packages/js/components/src/select-control/test/index.tsx similarity index 95% rename from packages/js/components/src/select-control/test/index.js rename to packages/js/components/src/select-control/test/index.tsx index 0cb6bfeb849..825fe0bdb1e 100644 --- a/packages/js/components/src/select-control/test/index.js +++ b/packages/js/components/src/select-control/test/index.tsx @@ -1,6 +1,7 @@ /** * External dependencies */ +import React from 'react'; import { render, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { createElement } from '@wordpress/element'; @@ -9,10 +10,11 @@ import { createElement } from '@wordpress/element'; * Internal dependencies */ import { SelectControl } from '../index'; +import { Option } from '../types'; describe( 'SelectControl', () => { const query = 'lorem'; - const options = [ + const options: Option[] = [ { key: '1', label: 'lorem 1', value: { id: '1' } }, { key: '2', label: 'lorem 2', value: { id: '2' } }, { key: '3', label: 'bar', value: { id: '3' } }, @@ -168,9 +170,9 @@ describe( 'SelectControl', () => { } ); it( 'changes the options on search', async () => { - const queriedOptions = []; + const queriedOptions: Option[] = []; // eslint-disable-next-line @typescript-eslint/no-shadow - const queryOptions = ( options, searchedQuery ) => { + const queryOptions = async ( options: Option[], searchedQuery: string | null ) => { if ( searchedQuery === 'test' ) { queriedOptions.push( { key: 'test-option', @@ -209,7 +211,7 @@ describe( 'SelectControl', () => { isSearchable showAllOnFocus options={ options } - onSearch={ () => options } + onSearch={ async () => options } onFilter={ () => options } onChange={ onChangeMock } /> @@ -229,7 +231,7 @@ describe( 'SelectControl', () => { isSearchable selected={ [ { ...options[ 0 ] } ] } options={ options } - onSearch={ () => options } + onSearch={ async () => options } onFilter={ () => options } onChange={ onChangeMock } /> @@ -258,7 +260,7 @@ describe( 'SelectControl', () => { isSearchable selected={ options[ 0 ].key } options={ options } - onSearch={ () => options } + onSearch={ async () => options } onFilter={ () => options } onChange={ onChangeMock } /> @@ -289,7 +291,7 @@ describe( 'SelectControl', () => { showAllOnFocus selected={ options[ 2 ].key } options={ options } - onSearch={ () => options } + onSearch={ async () => options } onFilter={ () => options } onChange={ onChangeMock } excludeSelectedOptions={ false } @@ -316,7 +318,7 @@ describe( 'SelectControl', () => { showAllOnFocus selected={ options[ 2 ].key } options={ options } - onSearch={ () => options } + onSearch={ async () => options } onFilter={ () => options } onChange={ onChangeMock } excludeSelectedOptions={ false } @@ -364,7 +366,7 @@ describe( 'SelectControl', () => { isSearchable selected={ [ { ...options[ 0 ] } ] } options={ options } - onSearch={ () => options } + onSearch={ async () => options } onFilter={ () => options } onChange={ onChangeMock } /> @@ -383,7 +385,7 @@ describe( 'SelectControl', () => { isSearchable selected={ options[ 0 ].key } options={ options } - onSearch={ () => options } + onSearch={ async () => options } onFilter={ () => options } onChange={ onChangeMock } /> @@ -416,9 +418,8 @@ describe( 'SelectControl', () => { const { getByRole } = render( options } + onSearch={ async () => options } onFilter={ () => options } - selected={ null } /> ); @@ -440,7 +441,7 @@ describe( 'SelectControl', () => { const { getByRole } = render( options } + onSearch={ async () => options } onFilter={ () => options } selected={ options[ 1 ].key } excludeSelectedOptions={ false } @@ -465,7 +466,7 @@ describe( 'SelectControl', () => { const { getByRole } = render( options } + onSearch={ async () => options } onFilter={ () => options } selected={ options[ options.length - 1 ].key } excludeSelectedOptions={ false } @@ -490,9 +491,8 @@ describe( 'SelectControl', () => { const { getByRole } = render( options } + onSearch={ async () => options } onFilter={ () => options } - selected={ null } /> ); @@ -514,9 +514,8 @@ describe( 'SelectControl', () => { const { getByRole, queryByRole } = render( options } + onSearch={ async () => options } onFilter={ () => options } - selected={ null } /> ); @@ -537,9 +536,8 @@ describe( 'SelectControl', () => { const { getByRole } = render( options } + onSearch={ async () => options } onFilter={ () => options } - selected={ null } excludeSelectedOptions={ false } onChange={ onChangeMock } /> diff --git a/packages/js/components/src/select-control/types.ts b/packages/js/components/src/select-control/types.ts new file mode 100644 index 00000000000..6744615cdf6 --- /dev/null +++ b/packages/js/components/src/select-control/types.ts @@ -0,0 +1,9 @@ +export type Option = { + key: string; + label: string; + isDisabled?: boolean; + keywords?: Array< string >; + value?: unknown; +}; + +export type Selected = string | Option[]; diff --git a/packages/js/components/src/sortable/sortable.tsx b/packages/js/components/src/sortable/sortable.tsx index c1e1f4e78f6..fbc175d45e5 100644 --- a/packages/js/components/src/sortable/sortable.tsx +++ b/packages/js/components/src/sortable/sortable.tsx @@ -110,7 +110,7 @@ export const Sortable = ( { // Items before the current item cause a one off error when // removed from the old array and spliced into the new array. - // TODO: Issue with dragging into same position having to do with isBefore returning true intially. + // TODO: Issue with dragging into same position having to do with isBefore returning true initially. let targetIndex = dragIndex < index ? index : index + 1; if ( isBefore( event, isHorizontal ) ) { targetIndex--; diff --git a/packages/js/components/src/table/README.md b/packages/js/components/src/table/README.md index 54378b6dd25..2048d89a3b6 100644 --- a/packages/js/components/src/table/README.md +++ b/packages/js/components/src/table/README.md @@ -61,7 +61,7 @@ Name | Type | Default | Description `ids` | Array | `null` | A list of IDs, matching to the row list so that ids[ 0 ] contains the object ID for the object displayed in row[ 0 ] `isLoading` | Boolean | `false` | Defines if the table contents are loading. It will display `TablePlaceholder` component instead of `Table` if that's the case `onQueryChange` | Function | `noop` | A function which returns a callback function to update the query string for a given `param` -`onColumnsChange` | Function | `noop` | A function which returns a callback function which is called upon the user changing the visiblity of columns +`onColumnsChange` | Function | `noop` | A function which returns a callback function which is called upon the user changing the visibility of columns `onSearch` | Function | `noop` | A function which is called upon the user searching in the table header `onSort` | Function | `undefined` | A function which is called upon the user changing the sorting of the table `downloadable` | Boolean | `false` | Whether the table must be downloadable. If true, the download button will appear diff --git a/packages/js/components/src/table/types.ts b/packages/js/components/src/table/types.ts index 79b61e92a3d..6bd3b086894 100644 --- a/packages/js/components/src/table/types.ts +++ b/packages/js/components/src/table/types.ts @@ -158,7 +158,7 @@ export type TableCardProps = CommonTableProps & { // eslint-disable-next-line @typescript-eslint/no-explicit-any onQueryChange?: ( param: string ) => ( ...props: any ) => void; /** - * A function which returns a callback function which is called upon the user changing the visiblity of columns. + * A function which returns a callback function which is called upon the user changing the visibility of columns. */ onColumnsChange?: ( showCols: Array< string >, key?: string ) => void; /** diff --git a/packages/js/data/PREVIOUS_CHANGELOG.md b/packages/js/data/PREVIOUS_CHANGELOG.md index 2bb5eb68baf..e0886007ce1 100644 --- a/packages/js/data/PREVIOUS_CHANGELOG.md +++ b/packages/js/data/PREVIOUS_CHANGELOG.md @@ -22,7 +22,7 @@ ## Breaking changes - Fix the batch fetch logic for the options data store. #7587 -- Add backwards compability for old function format. #7688 +- Add backwards compatibility for old function format. #7688 - Add console warning for inbox note contents exceeding 320 characters and add dompurify dependency. #7869 - Fix race condition in data package's options module. #7947 - Remove dev dependency `@woocommerce/wc-admin-settings`. #8057 diff --git a/packages/js/data/changelog/fix-37502 b/packages/js/data/changelog/fix-37502 new file mode 100644 index 00000000000..115429c4b01 --- /dev/null +++ b/packages/js/data/changelog/fix-37502 @@ -0,0 +1,4 @@ +Significance: patch +Type: tweak + +Correct spelling errors diff --git a/packages/js/experimental/changelog/fix-37502 b/packages/js/experimental/changelog/fix-37502 new file mode 100644 index 00000000000..115429c4b01 --- /dev/null +++ b/packages/js/experimental/changelog/fix-37502 @@ -0,0 +1,4 @@ +Significance: patch +Type: tweak + +Correct spelling errors diff --git a/packages/js/experimental/src/experimental-list/test/index.tsx b/packages/js/experimental/src/experimental-list/test/index.tsx index c37e714bc76..89e8f6889a7 100644 --- a/packages/js/experimental/src/experimental-list/test/index.tsx +++ b/packages/js/experimental/src/experimental-list/test/index.tsx @@ -163,7 +163,7 @@ describe( 'Experimental List', () => { } ); describe( 'ExperimentalListItemCollapse', () => { - it( 'should not render its children intially, but an extra list footer with show text', () => { + it( 'should not render its children initially, but an extra list footer with show text', () => { const { container } = render( { ); } ); - it( 'should call onVisible when visiblity sensor calls it', () => { + it( 'should call onVisible when visibility sensor calls it', () => { const onVisible = jest.fn(); const { getByText } = render( { expect( onVisible ).toHaveBeenCalledWith( note ); } ); - it( 'should call onVisible when visiblity sensor calls it, but only once', () => { + it( 'should call onVisible when visibility sensor calls it, but only once', () => { const onVisible = jest.fn(); const { getByText } = render( ) { + const blockProps = useBlockProps(); + const { label, help } = attributes; + const [ regularPrice, setRegularPrice ] = useEntityProp< string >( + 'postType', + 'product', + 'regular_price' + ); + const [ salePrice ] = useEntityProp< string >( + 'postType', + 'product', + 'sale_price' + ); + const context = useContext( CurrencyContext ); + const { getCurrencyConfig, formatAmount } = context; + const currencyConfig = getCurrencyConfig(); + const inputProps = useCurrencyInputProps( { + value: regularPrice, + setValue: setRegularPrice, + } ); + + const interpolatedHelp = help + ? createInterpolateElement( help, { + PricingTab: ( + { + recordEvent( 'product_pricing_help_click' ); + } } + /> + ), + } ) + : null; + + const regularPriceId = useInstanceId( + BaseControl, + 'wp-block-woocommerce-product-regular-price-field' + ) as string; + + const regularPriceValidationError = useValidation( + 'product/regular_price', + function regularPriceValidator() { + const listPrice = Number.parseFloat( regularPrice ); + if ( listPrice ) { + if ( listPrice < 0 ) { + return __( + 'List price must be greater than or equals to zero.', + 'woocommerce' + ); + } + if ( + salePrice && + listPrice <= Number.parseFloat( salePrice ) + ) { + return __( + 'List price must be greater than the sale price.', + 'woocommerce' + ); + } + } + } + ); + + return ( +
+ + + +
+ ); +} diff --git a/packages/js/product-editor/src/blocks/regular-price/editor.scss b/packages/js/product-editor/src/blocks/regular-price/editor.scss new file mode 100644 index 00000000000..9115cf687cd --- /dev/null +++ b/packages/js/product-editor/src/blocks/regular-price/editor.scss @@ -0,0 +1,11 @@ +.wp-block-woocommerce-product-regular-price-field { + .components-currency-control { + .components-input-control__prefix { + color: $gray-700; + } + + .components-input-control__input { + text-align: right; + } + } +} \ No newline at end of file diff --git a/packages/js/product-editor/src/blocks/regular-price/index.ts b/packages/js/product-editor/src/blocks/regular-price/index.ts new file mode 100644 index 00000000000..f838788b632 --- /dev/null +++ b/packages/js/product-editor/src/blocks/regular-price/index.ts @@ -0,0 +1,28 @@ +/** + * External dependencies + */ +import { BlockConfiguration } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import { initBlock } from '../../utils/init-blocks'; +import blockConfiguration from './block.json'; +import { Edit } from './edit'; +import { SalePriceBlockAttributes } from './types'; + +const { name, ...metadata } = + blockConfiguration as BlockConfiguration< SalePriceBlockAttributes >; + +export { metadata, name }; + +export const settings: Partial< + BlockConfiguration< SalePriceBlockAttributes > +> = { + example: {}, + edit: Edit, +}; + +export function init() { + return initBlock( { name, metadata, settings } ); +} diff --git a/packages/js/product-editor/src/blocks/regular-price/types.ts b/packages/js/product-editor/src/blocks/regular-price/types.ts new file mode 100644 index 00000000000..99ceaa60e80 --- /dev/null +++ b/packages/js/product-editor/src/blocks/regular-price/types.ts @@ -0,0 +1,9 @@ +/** + * External dependencies + */ +import { BlockAttributes } from '@wordpress/blocks'; + +export interface SalePriceBlockAttributes extends BlockAttributes { + label: string; + help?: string; +} diff --git a/packages/js/product-editor/src/blocks/sale-price/block.json b/packages/js/product-editor/src/blocks/sale-price/block.json new file mode 100644 index 00000000000..51babacca28 --- /dev/null +++ b/packages/js/product-editor/src/blocks/sale-price/block.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "woocommerce/product-sale-price-field", + "description": "A product price block with currency display.", + "title": "Product sale price", + "category": "widgets", + "keywords": [ "products", "price" ], + "textdomain": "default", + "attributes": { + "label": { + "type": "string", + "__experimentalRole": "content" + }, + "help": { + "type": "string" + } + }, + "supports": { + "align": false, + "html": false, + "multiple": false, + "reusable": false, + "inserter": false, + "lock": false, + "__experimentalToolbar": false + }, + "editorStyle": "file:./editor.css" +} diff --git a/packages/js/product-editor/src/blocks/sale-price/edit.tsx b/packages/js/product-editor/src/blocks/sale-price/edit.tsx new file mode 100644 index 00000000000..b4104aedd34 --- /dev/null +++ b/packages/js/product-editor/src/blocks/sale-price/edit.tsx @@ -0,0 +1,104 @@ +/** + * External dependencies + */ +import classNames from 'classnames'; +import { CurrencyContext } from '@woocommerce/currency'; +import { useBlockProps } from '@wordpress/block-editor'; +import { BlockEditProps } from '@wordpress/blocks'; +import { useInstanceId } from '@wordpress/compose'; +import { useEntityProp } from '@wordpress/core-data'; +import { createElement, useContext } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { + BaseControl, + // @ts-expect-error `__experimentalInputControl` does exist. + __experimentalInputControl as InputControl, +} from '@wordpress/components'; + +/** + * Internal dependencies + */ +import { useCurrencyInputProps } from '../../hooks/use-currency-input-props'; +import { formatCurrencyDisplayValue } from '../../utils'; +import { SalePriceBlockAttributes } from './types'; +import { useValidation } from '../../hooks/use-validation'; + +export function Edit( { + attributes, +}: BlockEditProps< SalePriceBlockAttributes > ) { + const blockProps = useBlockProps(); + const { label, help } = attributes; + const [ regularPrice ] = useEntityProp< string >( + 'postType', + 'product', + 'regular_price' + ); + const [ salePrice, setSalePrice ] = useEntityProp< string >( + 'postType', + 'product', + 'sale_price' + ); + const context = useContext( CurrencyContext ); + const { getCurrencyConfig, formatAmount } = context; + const currencyConfig = getCurrencyConfig(); + const inputProps = useCurrencyInputProps( { + value: salePrice, + setValue: setSalePrice, + } ); + + const salePriceId = useInstanceId( + BaseControl, + 'wp-block-woocommerce-product-sale-price-field' + ) as string; + + const salePriceValidationError = useValidation( + 'product/sale_price', + function salePriceValidator() { + if ( salePrice ) { + if ( Number.parseFloat( salePrice ) < 0 ) { + return __( + 'Sale price must be greater than or equals to zero.', + 'woocommerce' + ); + } + const listPrice = Number.parseFloat( regularPrice ); + if ( + ! listPrice || + listPrice <= Number.parseFloat( salePrice ) + ) { + return __( + 'Sale price must be lower than the list price.', + 'woocommerce' + ); + } + } + } + ); + + return ( +
+ + + +
+ ); +} diff --git a/packages/js/product-editor/src/blocks/sale-price/editor.scss b/packages/js/product-editor/src/blocks/sale-price/editor.scss new file mode 100644 index 00000000000..5d1cf4d7d84 --- /dev/null +++ b/packages/js/product-editor/src/blocks/sale-price/editor.scss @@ -0,0 +1,11 @@ +.wp-block-woocommerce-product-sale-price-field { + .components-currency-control { + .components-input-control__prefix { + color: $gray-700; + } + + .components-input-control__input { + text-align: right; + } + } +} \ No newline at end of file diff --git a/packages/js/product-editor/src/blocks/sale-price/index.ts b/packages/js/product-editor/src/blocks/sale-price/index.ts new file mode 100644 index 00000000000..f838788b632 --- /dev/null +++ b/packages/js/product-editor/src/blocks/sale-price/index.ts @@ -0,0 +1,28 @@ +/** + * External dependencies + */ +import { BlockConfiguration } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import { initBlock } from '../../utils/init-blocks'; +import blockConfiguration from './block.json'; +import { Edit } from './edit'; +import { SalePriceBlockAttributes } from './types'; + +const { name, ...metadata } = + blockConfiguration as BlockConfiguration< SalePriceBlockAttributes >; + +export { metadata, name }; + +export const settings: Partial< + BlockConfiguration< SalePriceBlockAttributes > +> = { + example: {}, + edit: Edit, +}; + +export function init() { + return initBlock( { name, metadata, settings } ); +} diff --git a/packages/js/product-editor/src/blocks/sale-price/types.ts b/packages/js/product-editor/src/blocks/sale-price/types.ts new file mode 100644 index 00000000000..99ceaa60e80 --- /dev/null +++ b/packages/js/product-editor/src/blocks/sale-price/types.ts @@ -0,0 +1,9 @@ +/** + * External dependencies + */ +import { BlockAttributes } from '@wordpress/blocks'; + +export interface SalePriceBlockAttributes extends BlockAttributes { + label: string; + help?: string; +} diff --git a/packages/js/product-editor/src/blocks/shipping-dimensions/edit.tsx b/packages/js/product-editor/src/blocks/shipping-dimensions/edit.tsx index c162f637a74..80f09571c95 100644 --- a/packages/js/product-editor/src/blocks/shipping-dimensions/edit.tsx +++ b/packages/js/product-editor/src/blocks/shipping-dimensions/edit.tsx @@ -205,6 +205,17 @@ export function Edit( {}: BlockEditProps< ShippingDimensionsBlockAttributes > )
diff --git a/packages/js/product-editor/src/blocks/shipping-dimensions/editor.scss b/packages/js/product-editor/src/blocks/shipping-dimensions/editor.scss index 38bd27dc95e..668f03a6f9e 100644 --- a/packages/js/product-editor/src/blocks/shipping-dimensions/editor.scss +++ b/packages/js/product-editor/src/blocks/shipping-dimensions/editor.scss @@ -18,5 +18,6 @@ width: 100%; height: 100%; padding: $gap; + overflow: visible; } } diff --git a/packages/js/product-editor/src/blocks/style.scss b/packages/js/product-editor/src/blocks/style.scss index 16869c3e0f4..87bb061a386 100644 --- a/packages/js/product-editor/src/blocks/style.scss +++ b/packages/js/product-editor/src/blocks/style.scss @@ -6,6 +6,8 @@ @import 'inventory-sku/editor.scss'; @import 'name/editor.scss'; @import 'pricing/editor.scss'; +@import 'regular-price/editor.scss'; +@import 'sale-price/editor.scss'; @import 'schedule-sale/editor.scss'; @import 'section/editor.scss'; @import 'shipping-dimensions/editor.scss'; diff --git a/packages/js/product-editor/src/blocks/summary/edit.tsx b/packages/js/product-editor/src/blocks/summary/edit.tsx index f9d07c81c45..802a9898d47 100644 --- a/packages/js/product-editor/src/blocks/summary/edit.tsx +++ b/packages/js/product-editor/src/blocks/summary/edit.tsx @@ -5,8 +5,9 @@ import { __ } from '@wordpress/i18n'; import { createElement } from '@wordpress/element'; import { BlockEditProps } from '@wordpress/blocks'; import { BaseControl } from '@wordpress/components'; +import { useDispatch } from '@wordpress/data'; import { useEntityProp } from '@wordpress/core-data'; -import uniqueId from 'lodash/uniqueId'; +import { useInstanceId } from '@wordpress/compose'; import classNames from 'classnames'; import { // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -14,6 +15,7 @@ import { AlignmentControl, BlockControls, RichText, + store as blockEditorStore, useBlockProps, } from '@wordpress/block-editor'; @@ -32,12 +34,18 @@ export function Edit( { const blockProps = useBlockProps( { style: { direction }, } ); - const id = uniqueId(); + const contentId = useInstanceId( + Edit, + 'wp-block-woocommerce-product-summary-field__content' + ); const [ summary, setSummary ] = useEntityProp< string >( 'postType', 'product', 'short_description' ); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore No types for this exist yet. + const { clearSelectedBlock } = useDispatch( blockEditorStore ); function handleAlignmentChange( value: SummaryAttributes[ 'align' ] ) { setAttributes( { align: value } ); @@ -47,8 +55,21 @@ export function Edit( { setAttributes( { direction: value } ); } + function handleBlur( event: React.FocusEvent< 'p', Element > ) { + const isToolbar = event.relatedTarget?.closest( + '.block-editor-block-contextual-toolbar' + ); + if ( ! isToolbar ) { + clearSelectedBlock(); + } + } + return ( -
+
{ /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */ } { /* @ts-ignore No types for this exist yet. */ } @@ -65,26 +86,29 @@ export function Edit( { - +
+ +
); diff --git a/packages/js/product-editor/src/components/block-editor/block-editor.tsx b/packages/js/product-editor/src/components/block-editor/block-editor.tsx index 2c011c4ed7a..a53e7ca52ad 100644 --- a/packages/js/product-editor/src/components/block-editor/block-editor.tsx +++ b/packages/js/product-editor/src/components/block-editor/block-editor.tsx @@ -18,7 +18,6 @@ import { BlockTools, EditorSettings, EditorBlockListSettings, - WritingFlow, ObserveTyping, } from '@wordpress/block-editor'; // It doesn't seem to notice the External dependency block whn @ts-ignore is added. @@ -106,11 +105,9 @@ export function BlockEditor( { { /* @ts-ignore No types for this exist yet. */ } - - - - - + + +
diff --git a/packages/js/product-editor/src/components/details-categories-field/test/category-field.test.tsx b/packages/js/product-editor/src/components/details-categories-field/test/category-field.test.tsx index d0616672c2e..8f5b6a416e2 100644 --- a/packages/js/product-editor/src/components/details-categories-field/test/category-field.test.tsx +++ b/packages/js/product-editor/src/components/details-categories-field/test/category-field.test.tsx @@ -74,7 +74,6 @@ describe( 'CategoryField', () => { ); queryByPlaceholderText( 'Search or create category…' )?.focus(); - expect( queryAllByText( 'Test' ) ).toHaveLength( 2 ); - expect( queryAllByText( 'Clothing' ) ).toHaveLength( 2 ); + expect( queryAllByText( 'Test, Clothing' ) ).toHaveLength( 1 ); } ); } ); diff --git a/packages/js/product-editor/src/components/iframe-editor/iframe-editor.tsx b/packages/js/product-editor/src/components/iframe-editor/iframe-editor.tsx index 2bf508964d0..3f546bfe805 100644 --- a/packages/js/product-editor/src/components/iframe-editor/iframe-editor.tsx +++ b/packages/js/product-editor/src/components/iframe-editor/iframe-editor.tsx @@ -4,13 +4,8 @@ import { BlockInstance } from '@wordpress/blocks'; import { Popover } from '@wordpress/components'; import { useDispatch, useSelect } from '@wordpress/data'; -import { - createElement, - useCallback, - useEffect, - useState, -} from '@wordpress/element'; -import { useDebounce, useResizeObserver } from '@wordpress/compose'; +import { createElement, useEffect, useState } from '@wordpress/element'; +import { useResizeObserver } from '@wordpress/compose'; import { BlockEditorProvider, BlockInspector, @@ -66,15 +61,6 @@ export function IframeEditor( { updateSettings( productBlockEditorSettings ); }, [] ); - const handleChange = useCallback( - ( updatedBlocks: BlockInstance[] ) => { - onChange( updatedBlocks ); - }, - [ onChange ] - ); - - const debouncedOnChange = useDebounce( handleChange, 200 ); - return (
{ setBlocks( updatedBlocks ); - debouncedOnChange( updatedBlocks ); + onChange( updatedBlocks ); } } useSubRegistry={ true } > @@ -104,7 +90,13 @@ export function IframeEditor( { { /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */ } { /* @ts-ignore */ } - { onClose && } + { onClose && ( + { + setTimeout( onClose, 550 ); + } } + /> + ) } { /* Side A */ } + { /* Line A */ } - { /* Side B */ } + { /* Line C */ } + + { /* Line B */ } + - + { /* Label C */ } + { labels.C ? ( + + { labels.C } + + ) : ( + + ) } - { /* Arrow A */ } - - { /* Arrow B */ } - - { /* Arrow C */ } - + { /* Label B */ } + { labels.B ? ( + + { labels.B } + + ) : ( + + ) } - { /* Letter A */ } - - { /* Letter B */ } - - { /* Letter C */ } - + { /* Label A */ } + { labels.A ? ( + + { labels.A } + + ) : ( + + ) } ); } diff --git a/packages/js/product-editor/src/components/shipping-dimensions-image/types.ts b/packages/js/product-editor/src/components/shipping-dimensions-image/types.ts index 8824a9c248f..119c9cf5c4e 100644 --- a/packages/js/product-editor/src/components/shipping-dimensions-image/types.ts +++ b/packages/js/product-editor/src/components/shipping-dimensions-image/types.ts @@ -2,4 +2,9 @@ export type HighlightSides = 'A' | 'B' | 'C'; export type ShippingDimensionsImageProps = React.SVGProps< SVGSVGElement > & { highlight?: HighlightSides; + labels?: { + A?: string | number; + B?: string | number; + C?: string | number; + }; }; diff --git a/plugins/woocommerce-admin/client/activity-panel/test/index.js b/plugins/woocommerce-admin/client/activity-panel/test/index.js index 891a575fcd7..b63524bb4e3 100644 --- a/plugins/woocommerce-admin/client/activity-panel/test/index.js +++ b/plugins/woocommerce-admin/client/activity-panel/test/index.js @@ -219,7 +219,7 @@ describe( 'Activity Panel', () => { expect( getByText( 'Finish setup' ) ).toBeInTheDocument(); } ); - it( 'should not render the finish setup link when a user does not have capabilties', () => { + it( 'should not render the finish setup link when a user does not have capabilities', () => { useUser.mockImplementation( () => ( { currentUserCan: () => false, } ) ); diff --git a/plugins/woocommerce-admin/client/analytics/components/report-filters/index.js b/plugins/woocommerce-admin/client/analytics/components/report-filters/index.js index c2313f1b4a6..03ff9dad74a 100644 --- a/plugins/woocommerce-admin/client/analytics/components/report-filters/index.js +++ b/plugins/woocommerce-admin/client/analytics/components/report-filters/index.js @@ -45,7 +45,7 @@ class ReportFilters extends Component { // This event gets triggered in the following cases. // 1. Select "Single product" and choose a product. // 2. Select "Comparison" or any other filter types. - // The comparsion and other filter types require a user to click + // The comparison and other filter types require a user to click // a button to execute a query, so this is not a good place to // trigger a CES survey for those. const triggerCesFor = [ diff --git a/plugins/woocommerce-admin/client/analytics/components/report-table/index.js b/plugins/woocommerce-admin/client/analytics/components/report-table/index.js index 22b5dbe4803..89f020645ce 100644 --- a/plugins/woocommerce-admin/client/analytics/components/report-table/index.js +++ b/plugins/woocommerce-admin/client/analytics/components/report-table/index.js @@ -138,7 +138,7 @@ const ReportTable = ( props ) => { }; const filterShownHeaders = ( headers, hiddenKeys ) => { - // If no user preferences, set visibilty based on column default. + // If no user preferences, set visibility based on column default. if ( ! hiddenKeys ) { return headers.map( ( header ) => ( { ...header, @@ -146,7 +146,7 @@ const ReportTable = ( props ) => { } ) ); } - // Set visibilty based on user preferences. + // Set visibility based on user preferences. return headers.map( ( header ) => ( { ...header, visible: header.required || ! hiddenKeys.includes( header.key ), diff --git a/plugins/woocommerce-admin/client/analytics/report/README.md b/plugins/woocommerce-admin/client/analytics/report/README.md index 21ddb7163db..fd7261eada2 100644 --- a/plugins/woocommerce-admin/client/analytics/report/README.md +++ b/plugins/woocommerce-admin/client/analytics/report/README.md @@ -24,7 +24,7 @@ Each menu item is defined by an array containing `id`, `title`, `parent`, and `p - `report` (string): The report's id. - `title` (string): The title shown in the sidebar. -- `parent` (string): The item's parent in the navigational heirarchy. +- `parent` (string): The item's parent in the navigational hierarchy. - `path` (string): The report's relative path. Next, hook into the JavaScript reports filter, `woocommerce_admin_reports_list`, to add a report component. diff --git a/plugins/woocommerce-admin/client/analytics/settings/README.md b/plugins/woocommerce-admin/client/analytics/settings/README.md index ddc4f7d6a53..aab2d248178 100644 --- a/plugins/woocommerce-admin/client/analytics/settings/README.md +++ b/plugins/woocommerce-admin/client/analytics/settings/README.md @@ -1,7 +1,7 @@ Settings ======= -The settings used to modify the way data is retreived or displayed in WooCommerce reports. +The settings used to modify the way data is retrieved or displayed in WooCommerce reports. ## Extending Settings diff --git a/plugins/woocommerce-admin/client/dashboard/default-sections.js b/plugins/woocommerce-admin/client/dashboard/default-sections.js index 4f01bbbb1ab..b7176e586ff 100644 --- a/plugins/woocommerce-admin/client/dashboard/default-sections.js +++ b/plugins/woocommerce-admin/client/dashboard/default-sections.js @@ -48,7 +48,7 @@ const DEFAULT_SECTIONS_FILTER = 'woocommerce_dashboard_default_sections'; * @property {string} key Unique identifying string. * @property {Node} component React component to render. * @property {string} title Title. - * @property {boolean} isVisible The default visibilty. + * @property {boolean} isVisible The default visibility. * @property {Node} icon Section icon. * @property {Array.} hiddenBlocks Blocks that are hidden by default. */ diff --git a/plugins/woocommerce-admin/client/homescreen/mobile-app-modal/components/useSendMagicLink.tsx b/plugins/woocommerce-admin/client/homescreen/mobile-app-modal/components/useSendMagicLink.tsx index 01671226428..5eebbdb4964 100644 --- a/plugins/woocommerce-admin/client/homescreen/mobile-app-modal/components/useSendMagicLink.tsx +++ b/plugins/woocommerce-admin/client/homescreen/mobile-app-modal/components/useSendMagicLink.tsx @@ -47,7 +47,7 @@ export const useSendMagicLink = () => { setRequestState( SendMagicLinkStates.ERROR ); createNotice( 'error', - __( 'Sorry, an unknown error occured.', 'woocommerce' ) + __( 'Sorry, an unknown error occurred.', 'woocommerce' ) ); } } ) diff --git a/plugins/woocommerce-admin/client/layout/controller.js b/plugins/woocommerce-admin/client/layout/controller.js index 951ca12b227..68536a68837 100644 --- a/plugins/woocommerce-admin/client/layout/controller.js +++ b/plugins/woocommerce-admin/client/layout/controller.js @@ -289,7 +289,7 @@ export const getPages = () => { container: SettingsGroup, path: '/settings/:page', breadcrumbs: ( { match } ) => { - // @todo This might need to be refactored to retreive groups via data store. + // @todo This might need to be refactored to retrieve groups via data store. const settingsPages = getAdminSetting( 'settingsPages' ); const page = settingsPages[ match.params.page ]; if ( ! page ) { diff --git a/plugins/woocommerce-admin/client/navigation/components/Item/index.js b/plugins/woocommerce-admin/client/navigation/components/Item/index.js index 07771664514..c08888e8b72 100644 --- a/plugins/woocommerce-admin/client/navigation/components/Item/index.js +++ b/plugins/woocommerce-admin/client/navigation/components/Item/index.js @@ -19,7 +19,7 @@ const Item = ( { item } ) => { // and should not be a tabbable element. /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ - // Only render a slot if a coresponding Fill exists and the item is not a category + // Only render a slot if a corresponding Fill exists and the item is not a category if ( hasFills && ! item.isCategory ) { return ( diff --git a/plugins/woocommerce-admin/client/navigation/utils.ts b/plugins/woocommerce-admin/client/navigation/utils.ts index 446f0b4f2bb..04c1bc4b1ac 100644 --- a/plugins/woocommerce-admin/client/navigation/utils.ts +++ b/plugins/woocommerce-admin/client/navigation/utils.ts @@ -190,7 +190,7 @@ export const sortMenuItems = ( menuItems: Item[] ): Item[] => { }; /** - * Get a flat tree structure of all Categories and thier children grouped by menuId + * Get a flat tree structure of all Categories and their children grouped by menuId * * @param {Array} menuItems Array of menu items. * @param {Function} currentUserCan Callback method passed the capability to determine if a menu item is visible. diff --git a/plugins/woocommerce-admin/client/products/edit-product-page.tsx b/plugins/woocommerce-admin/client/products/edit-product-page.tsx index 88ee6656876..3fbfffd31d2 100644 --- a/plugins/woocommerce-admin/client/products/edit-product-page.tsx +++ b/plugins/woocommerce-admin/client/products/edit-product-page.tsx @@ -94,7 +94,7 @@ const EditProductPage: React.FC = () => { ); useEffect( () => { - // used for determening the wasDeletedUsingAction condition. + // used for determining the wasDeletedUsingAction condition. if ( previousProductRef.current && product && diff --git a/plugins/woocommerce-admin/client/profile-wizard/steps/store-details/index.js b/plugins/woocommerce-admin/client/profile-wizard/steps/store-details/index.js index cfd148cab43..c2e0017ed5e 100644 --- a/plugins/woocommerce-admin/client/profile-wizard/steps/store-details/index.js +++ b/plugins/woocommerce-admin/client/profile-wizard/steps/store-details/index.js @@ -181,7 +181,7 @@ export class StoreDetails extends Component { * `await` and performs an update aysnchronously. This means the following * screen may not be initialized with correct profile settings. * - * This comment may be removed when a refactor to wp.data datatores is complete. + * This comment may be removed when a refactor to wp.data datastores is complete. */ if ( region !== 'US' && diff --git a/plugins/woocommerce-admin/client/stylesheets/shared/_reset.scss b/plugins/woocommerce-admin/client/stylesheets/shared/_reset.scss index 6ef60842cd2..57435876631 100644 --- a/plugins/woocommerce-admin/client/stylesheets/shared/_reset.scss +++ b/plugins/woocommerce-admin/client/stylesheets/shared/_reset.scss @@ -71,7 +71,7 @@ } } -// Temporary fix for compability with the Jetpack masterbar +// Temporary fix for compatibility with the Jetpack masterbar // See https://github.com/Automattic/jetpack/issues/9608 @include breakpoint( '<782px' ) { .jetpack-masterbar { diff --git a/plugins/woocommerce-admin/client/tasks/fills/appearance.js b/plugins/woocommerce-admin/client/tasks/fills/appearance.js index cf89d248ba4..87955209b89 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/appearance.js +++ b/plugins/woocommerce-admin/client/tasks/fills/appearance.js @@ -210,7 +210,7 @@ class Appearance extends Component { this.setState( { isUpdatingLogo: false } ); createNotice( 'success', - __( 'Store logo updated sucessfully', 'woocommerce' ) + __( 'Store logo updated successfully', 'woocommerce' ) ); this.completeStep(); } else { diff --git a/plugins/woocommerce-admin/client/tasks/fills/components/load-sample-product-confirm-modal.scss b/plugins/woocommerce-admin/client/tasks/fills/components/load-sample-product-confirm-modal.scss index e5eb6043b1c..1f72e411da4 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/components/load-sample-product-confirm-modal.scss +++ b/plugins/woocommerce-admin/client/tasks/fills/components/load-sample-product-confirm-modal.scss @@ -1,3 +1,17 @@ +.woocommerce-products-load-sample-product-confirm-modal-overlay { + @media (min-width: 783px ) { + & { + left: 35px; + } + } + + @include break-large { + & { + left: 160px; + } + } +} + .woocommerce-products-load-sample-product-confirm-modal { .components-truncate.components-text.woocommerce-confirmation-modal__message { color: $studio-gray-60; diff --git a/plugins/woocommerce-admin/client/tasks/fills/components/load-sample-product-confirm-modal.tsx b/plugins/woocommerce-admin/client/tasks/fills/components/load-sample-product-confirm-modal.tsx index 4d0cc29299d..3606be4142c 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/components/load-sample-product-confirm-modal.tsx +++ b/plugins/woocommerce-admin/client/tasks/fills/components/load-sample-product-confirm-modal.tsx @@ -22,6 +22,7 @@ export const LoadSampleProductConfirmModal: React.VFC< Props > = ( { return ( diff --git a/plugins/woocommerce-admin/client/tasks/fills/components/load-sample-product-modal.scss b/plugins/woocommerce-admin/client/tasks/fills/components/load-sample-product-modal.scss index ee3776206b7..0bd8f516962 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/components/load-sample-product-modal.scss +++ b/plugins/woocommerce-admin/client/tasks/fills/components/load-sample-product-modal.scss @@ -25,7 +25,8 @@ display: none; } - .components-modal__content { + .components-modal__content, + .components-modal__header + div { display: flex; flex-direction: column; align-items: center; diff --git a/plugins/woocommerce-admin/client/wp-admin-scripts/README.md b/plugins/woocommerce-admin/client/wp-admin-scripts/README.md index eb3ca08feb1..e3fd58aeb88 100644 --- a/plugins/woocommerce-admin/client/wp-admin-scripts/README.md +++ b/plugins/woocommerce-admin/client/wp-admin-scripts/README.md @@ -1,5 +1,5 @@ Scripts located in this directory are meant to be loaded on wp-admin pages outside the context of WooCommerce Admin, such as the post editor. Adding the script name to `wpAdminScripts` in the Webpack config will automatically build these scripts. -Scripts must be manually enqueued with any neccessary dependencies. For example, `onboarding-homepage-notice` uses the WooCommerce navigation package: +Scripts must be manually enqueued with any necessary dependencies. For example, `onboarding-homepage-notice` uses the WooCommerce navigation package: `wp_enqueue_script( 'onboarding-homepage-notice', Loader::get_url( 'wp-scripts/onboarding-homepage-notice.js' ), array( 'wc-navigation' ) );` \ No newline at end of file diff --git a/plugins/woocommerce-admin/docs/examples/extensions/payment-gateway-suggestions/woocommerce-admin-payment-gateway-suggestions-mock-installer.php b/plugins/woocommerce-admin/docs/examples/extensions/payment-gateway-suggestions/woocommerce-admin-payment-gateway-suggestions-mock-installer.php index abfe2926fbd..245a3009645 100644 --- a/plugins/woocommerce-admin/docs/examples/extensions/payment-gateway-suggestions/woocommerce-admin-payment-gateway-suggestions-mock-installer.php +++ b/plugins/woocommerce-admin/docs/examples/extensions/payment-gateway-suggestions/woocommerce-admin-payment-gateway-suggestions-mock-installer.php @@ -6,7 +6,7 @@ */ /** - * This is a workaround to bypass intalling the gateway plugins from WP.org. + * This is a workaround to bypass installing the gateway plugins from WP.org. * This is not necessary provided your suggestion is a valid WP.org plugin. * * @param array $response Response data. diff --git a/plugins/woocommerce-admin/docs/features/navigation.md b/plugins/woocommerce-admin/docs/features/navigation.md index 0d56e6bfe5c..bf68adc4d5f 100644 --- a/plugins/woocommerce-admin/docs/features/navigation.md +++ b/plugins/woocommerce-admin/docs/features/navigation.md @@ -12,7 +12,7 @@ The fastest way to get started is by creating an example plugin from WooCommerce `WC_EXT=add-navigation-items pnpm example --filter=woocommerce/client/admin` -This will create a new plugin that covers various features of the navigation and helps to register some intial items and categories within the new navigation menu. After running the command above, you can make edits directly to the files at `docs/examples/extensions/add-navigation-items` and they will be built and copied to your `wp-content/add-navigation-items` folder on save. +This will create a new plugin that covers various features of the navigation and helps to register some initial items and categories within the new navigation menu. After running the command above, you can make edits directly to the files at `docs/examples/extensions/add-navigation-items` and they will be built and copied to your `wp-content/add-navigation-items` folder on save. If you need to enable the WP Toolbar for debugging purposes in the new navigation, you can add the following filter to do so: diff --git a/plugins/woocommerce-admin/docs/features/onboarding-tasks.md b/plugins/woocommerce-admin/docs/features/onboarding-tasks.md index c8bdf815de9..c7748cbb694 100644 --- a/plugins/woocommerce-admin/docs/features/onboarding-tasks.md +++ b/plugins/woocommerce-admin/docs/features/onboarding-tasks.md @@ -32,8 +32,8 @@ $args = array( 'can_view' => 'US:CA' === wc_get_base_location(), 'level' => 3, // Priority level shown for extended tasks. 'time' => __( '2 minutes', 'plugin-text-domain' ), // Time string for time to complete the task. - 'is_dismissable' => false, // Determine if the taks is dismissable. - 'is_snoozeable' => true, // Determine if the taks is snoozeable. + 'is_dismissable' => false, // Determine if the task is dismissable. + 'is_snoozeable' => true, // Determine if the task is snoozeable. 'additional_info' => array( 'apples', 'oranges', 'bananas' ), // Additional info passed to the task. ) $task = new Task( $args ); diff --git a/plugins/woocommerce-admin/docs/features/onboarding.md b/plugins/woocommerce-admin/docs/features/onboarding.md index 4328d8aaf74..c2ae884fa93 100644 --- a/plugins/woocommerce-admin/docs/features/onboarding.md +++ b/plugins/woocommerce-admin/docs/features/onboarding.md @@ -70,7 +70,7 @@ To disconnect from WooCommerce.com, go to `WooCommerce > Extensions > WooCommerc ## Jetpack Connection -Using Jetpack & WooCommerce Shipping & Tax allows us to offer additional features to new WooCommerce users as well as simplify parts of the setup process. For example, we can do automated tax calculations for certain countries, significantly simplifying the tax task. To make this work, the user needs to be connected to a WordPress.com account. This also means development and testing of these features needs to be done on a Jetpack connected site. Search the MGS & the Feld Guide for additional resources on testing Jetpack with local setups. +Using Jetpack & WooCommerce Shipping & Tax allows us to offer additional features to new WooCommerce users as well as simplify parts of the setup process. For example, we can do automated tax calculations for certain countries, significantly simplifying the tax task. To make this work, the user needs to be connected to a WordPress.com account. This also means development and testing of these features needs to be done on a Jetpack connected site. Search the MGS & the Field Guide for additional resources on testing Jetpack with local setups. We have a special Jetpack connection flow designed specifically for WooCommerce onboarding, so that the user feels that they are connecting as part of a cohesive experience. To access this flow, we have a custom Jetpack connection endpoint [/wc-admin/plugins/connect-jetpack](https://github.com/woocommerce/woocommerce/blob/feba6a8dcd55d4f5c7edc05478369c76df082293/plugins/woocommerce/src/Admin/API/Plugins.php#L395-L417). diff --git a/plugins/woocommerce-admin/docs/features/payment-gateway-suggestions.md b/plugins/woocommerce-admin/docs/features/payment-gateway-suggestions.md index d65ce68127b..b8ea6752f42 100644 --- a/plugins/woocommerce-admin/docs/features/payment-gateway-suggestions.md +++ b/plugins/woocommerce-admin/docs/features/payment-gateway-suggestions.md @@ -6,7 +6,7 @@ After merchants click on a recommendation, plugins from this source will then wa ### Quick start -Gateway suggestions are retreived from a REST API and can be added via a remote JSON data source or filtered with the `woocommerce_admin_payment_gateway_suggestion_specs` filter. +Gateway suggestions are retrieved from a REST API and can be added via a remote JSON data source or filtered with the `woocommerce_admin_payment_gateway_suggestion_specs` filter. To quickly get started with an example plugin, run the following: @@ -22,7 +22,7 @@ If a user is not opted into marketplace suggestions or polling fails, the gatewa ## Remote Data Source Schema -The data source schema defines the recommended payment gateways and required plugins to kick of the setup process. The goal of this config is to provide the mininum amount of information possible to show a list of gateways and allow the gateways themselves to define specifics around configuration. +The data source schema defines the recommended payment gateways and required plugins to kick of the setup process. The goal of this config is to provide the minimum amount of information possible to show a list of gateways and allow the gateways themselves to define specifics around configuration. ```json [ diff --git a/plugins/woocommerce-admin/docs/woocommerce.com/README.md b/plugins/woocommerce-admin/docs/woocommerce.com/README.md index 2ed099f6070..06a17b6f40b 100644 --- a/plugins/woocommerce-admin/docs/woocommerce.com/README.md +++ b/plugins/woocommerce-admin/docs/woocommerce.com/README.md @@ -10,7 +10,7 @@ Currently, development efforts have been focused on two primary areas: ## Analytics -With WooCommerce installed, a new Analytics menu item is created in the wp-admin menu system. This menu item, and the reports contained insde of it are available to all wp-admin users that have the `view_woocommerce_reports` capability, so per a standard WooCommerce install this would give `shop_manager`s and `administrator`s access to the reports. +With WooCommerce installed, a new Analytics menu item is created in the wp-admin menu system. This menu item, and the reports contained inside of it are available to all wp-admin users that have the `view_woocommerce_reports` capability, so per a standard WooCommerce install this would give `shop_manager`s and `administrator`s access to the reports. Each report is quite unique with its own set of filtering options and chart types. To learn more about each individual report, please view the pages below: diff --git a/plugins/woocommerce-admin/docs/woocommerce.com/activity-panels.md b/plugins/woocommerce-admin/docs/woocommerce.com/activity-panels.md index 0b68f783f1a..58f83536679 100644 --- a/plugins/woocommerce-admin/docs/woocommerce.com/activity-panels.md +++ b/plugins/woocommerce-admin/docs/woocommerce.com/activity-panels.md @@ -2,7 +2,7 @@ The Activity Panel aims to be a "one stop shop" for managing your store - fulfill new orders, manage product inventory, moderate reviews, and get information about running your store. -Activity Panels can be accessed wherever the WooCommerce Admin nagivation bar is shown. +Activity Panels can be accessed wherever the WooCommerce Admin navigation bar is shown. ![Activity Panels Tabs Overview](images/activity-panels-tabs.png) diff --git a/plugins/woocommerce-admin/docs/woocommerce.com/analytics-basics.md b/plugins/woocommerce-admin/docs/woocommerce.com/analytics-basics.md index f846c5f7e28..1cb3b7e38f8 100644 --- a/plugins/woocommerce-admin/docs/woocommerce.com/analytics-basics.md +++ b/plugins/woocommerce-admin/docs/woocommerce.com/analytics-basics.md @@ -46,7 +46,7 @@ The _Summary Number_ tab gives you a quick view at the total figure for that met ### Chart ![Analytics Chart](analytics-basics-chart.png) -The charts on report pages offer quite a few options to customize the visualiztion of data. The data legend ( labeled A ) allows you to toggle the visiblity of the different data set periods. The _Interval Selector_ ( labeled B ) allows you to adjust the interval displayed in the chart. The options available here are dependent upon the length of the date range selected: +The charts on report pages offer quite a few options to customize the visualiztion of data. The data legend ( labeled A ) allows you to toggle the visibility of the different data set periods. The _Interval Selector_ ( labeled B ) allows you to adjust the interval displayed in the chart. The options available here are dependent upon the length of the date range selected: | Length of Date Range | Interval Options | |---|---| @@ -69,10 +69,10 @@ The table which displays the detailed data on Analytics reports also has a numbe Many columns in reports will allow you to click on the column header to sort the tabular data by that value, and to either sort by that value in ascending or descending order. Simply click the column header to sort by that value, and click it again to change between ascending and descending sort. -### Toggle Column Visiblity +### Toggle Column Visibility ![Analytics Table Column Sorting](analytics-table-column-visbility.png) -If a report contains a data column that you don't need to be displayed, you can adjust the visiblity of it by using the visibility menu on the right side of the table header. Click the column name in the menu to change the visibility of the column. Your visibility selections are persisted to your user preferences for each report, so on subsequent visits to that report, the columns you have previously toggled off will not be displayed. +If a report contains a data column that you don't need to be displayed, you can adjust the visibility of it by using the visibility menu on the right side of the table header. Click the column name in the menu to change the visibility of the column. Your visibility selections are persisted to your user preferences for each report, so on subsequent visits to that report, the columns you have previously toggled off will not be displayed. ### CSV Download ![Analytics Table csv Download](analytics-table-download-button.png) @@ -89,4 +89,4 @@ If your selected date range results in a data set that spans more then one page When the data displayed in the table is larger than the default single page size of 25, some pagination options will appear in the table footer area. Directional buttons, labeled `<` and `>` enable you to move backwards and forwards between pages, and a text field will allow you to jump to a specific page number. Furthermore you can change the number of rows to display per page. ### Table Search Box -On some reports, a search box is displayed in the table header area as well. For details on what the search box does on a given report, please refer to the associated documentaiton page for that report. \ No newline at end of file +On some reports, a search box is displayed in the table header area as well. For details on what the search box does on a given report, please refer to the associated documentation page for that report. \ No newline at end of file diff --git a/plugins/woocommerce-admin/docs/woocommerce.com/analytics-settings.md b/plugins/woocommerce-admin/docs/woocommerce.com/analytics-settings.md index 007c226f256..90df3b1f78b 100644 --- a/plugins/woocommerce-admin/docs/woocommerce.com/analytics-settings.md +++ b/plugins/woocommerce-admin/docs/woocommerce.com/analytics-settings.md @@ -12,7 +12,7 @@ WooCommerce Admin is pre-configured with default settings for WooCommerce Analyt ![Excluded statuses settings](images/settings-excluded-statuses.png) -In this section, statuses that are **unchecked** are **included** in anayltics reports. **Checked** statuses are **excluded**. If your store uses custom order statuses, those statuses are included in the reports by default. They will be listed in this section under `Custom Statuses` and can be excluded via the status checkbox. +In this section, statuses that are **unchecked** are **included** in analytics reports. **Checked** statuses are **excluded**. If your store uses custom order statuses, those statuses are included in the reports by default. They will be listed in this section under `Custom Statuses` and can be excluded via the status checkbox. ### Default Date Range diff --git a/plugins/woocommerce-beta-tester/changelog/fix-37502 b/plugins/woocommerce-beta-tester/changelog/fix-37502 new file mode 100644 index 00000000000..115429c4b01 --- /dev/null +++ b/plugins/woocommerce-beta-tester/changelog/fix-37502 @@ -0,0 +1,4 @@ +Significance: patch +Type: tweak + +Correct spelling errors diff --git a/plugins/woocommerce-beta-tester/woocommerce-beta-tester.php b/plugins/woocommerce-beta-tester/woocommerce-beta-tester.php index 3f8f9d7d8f1..9eb79c1a58f 100644 --- a/plugins/woocommerce-beta-tester/woocommerce-beta-tester.php +++ b/plugins/woocommerce-beta-tester/woocommerce-beta-tester.php @@ -44,7 +44,7 @@ function _wc_beta_tester_load_textdomain() { add_action( 'plugins_loaded', '_wc_beta_tester_load_textdomain' ); /** - * Boostrap plugin. + * Bootstrap plugin. */ function _wc_beta_tester_bootstrap() { diff --git a/plugins/woocommerce/changelog/26827-checkout-place-order-events b/plugins/woocommerce/changelog/26827-checkout-place-order-events new file mode 100644 index 00000000000..39538af1f4c --- /dev/null +++ b/plugins/woocommerce/changelog/26827-checkout-place-order-events @@ -0,0 +1,4 @@ +Significance: patch +Type: tweak + +Makes more information available to handlers for the `checkout_place_order` (and related) events. diff --git a/plugins/woocommerce/changelog/add-37985 b/plugins/woocommerce/changelog/add-37985 new file mode 100644 index 00000000000..fc2c2d5017d --- /dev/null +++ b/plugins/woocommerce/changelog/add-37985 @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add Sale price validation#37985 diff --git a/plugins/woocommerce/changelog/dev-37989_allow_dropdown_recording_for_wc_settings b/plugins/woocommerce/changelog/dev-37989_allow_dropdown_recording_for_wc_settings new file mode 100644 index 00000000000..82e22e14ca0 --- /dev/null +++ b/plugins/woocommerce/changelog/dev-37989_allow_dropdown_recording_for_wc_settings @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Modify 'WC_Settings_Tracking' to allow dropdown options recording for WooCommerce Settings diff --git a/plugins/woocommerce/changelog/e2e-pw-add-merchant-create-post b/plugins/woocommerce/changelog/e2e-pw-add-merchant-create-post new file mode 100644 index 00000000000..1be8cca7d69 --- /dev/null +++ b/plugins/woocommerce/changelog/e2e-pw-add-merchant-create-post @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add e2e test for Merchant > Posts > Can create a new post diff --git a/plugins/woocommerce/changelog/fix-37502 b/plugins/woocommerce/changelog/fix-37502 new file mode 100644 index 00000000000..115429c4b01 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-37502 @@ -0,0 +1,4 @@ +Significance: patch +Type: tweak + +Correct spelling errors diff --git a/plugins/woocommerce/changelog/fix-k6-api-40X-errors b/plugins/woocommerce/changelog/fix-k6-api-40X-errors index ab162eedf69..a9cf0d4ef6e 100644 --- a/plugins/woocommerce/changelog/fix-k6-api-40X-errors +++ b/plugins/woocommerce/changelog/fix-k6-api-40X-errors @@ -1,4 +1,4 @@ Significance: patch Type: fix -skip k6 api order RUD tests on non-existant order when C test fails +skip k6 api order RUD tests on non-existent order when C test fails diff --git a/plugins/woocommerce/changelog/fix-load-sample-product-modal b/plugins/woocommerce/changelog/fix-load-sample-product-modal new file mode 100644 index 00000000000..c0f718c3c16 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-load-sample-product-modal @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix loading sample product's progress message is misaligned if Gutenberg plugin is enabled diff --git a/plugins/woocommerce/changelog/pr-35232 b/plugins/woocommerce/changelog/pr-35232 new file mode 100644 index 00000000000..de028255c9e --- /dev/null +++ b/plugins/woocommerce/changelog/pr-35232 @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +fix logout vs log out typo \ No newline at end of file diff --git a/plugins/woocommerce/changelog/update-change-product-category-metabox-enqueue b/plugins/woocommerce/changelog/update-change-product-category-metabox-enqueue new file mode 100644 index 00000000000..b79b4c21182 --- /dev/null +++ b/plugins/woocommerce/changelog/update-change-product-category-metabox-enqueue @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Change product-category-metabox JS/style enqueue logic diff --git a/plugins/woocommerce/client/legacy/js/frontend/checkout.js b/plugins/woocommerce/client/legacy/js/frontend/checkout.js index 91a33b69329..02f31aa3eb8 100644 --- a/plugins/woocommerce/client/legacy/js/frontend/checkout.js +++ b/plugins/woocommerce/client/legacy/js/frontend/checkout.js @@ -477,7 +477,7 @@ jQuery( function( $ ) { // Trigger a handler to let gateways manipulate the checkout if needed // eslint-disable-next-line max-len - if ( $form.triggerHandler( 'checkout_place_order' ) !== false && $form.triggerHandler( 'checkout_place_order_' + wc_checkout_form.get_payment_method() ) !== false ) { + if ( $form.triggerHandler( 'checkout_place_order', [ wc_checkout_form ] ) !== false && $form.triggerHandler( 'checkout_place_order_' + wc_checkout_form.get_payment_method(), [ wc_checkout_form ] ) !== false ) { $form.addClass( 'processing' ); @@ -525,7 +525,7 @@ jQuery( function( $ ) { wc_checkout_form.detachUnloadEventsOnSubmit(); try { - if ( 'success' === result.result && $form.triggerHandler( 'checkout_place_order_success', result ) !== false ) { + if ( 'success' === result.result && $form.triggerHandler( 'checkout_place_order_success', [ result, wc_checkout_form ] ) !== false ) { if ( -1 === result.redirect.indexOf( 'https://' ) || -1 === result.redirect.indexOf( 'http://' ) ) { window.location = result.redirect; } else { diff --git a/plugins/woocommerce/client/legacy/js/frontend/woocommerce.js b/plugins/woocommerce/client/legacy/js/frontend/woocommerce.js index b10ad50d951..734d73cced2 100644 --- a/plugins/woocommerce/client/legacy/js/frontend/woocommerce.js +++ b/plugins/woocommerce/client/legacy/js/frontend/woocommerce.js @@ -79,7 +79,7 @@ jQuery( function( $ ) { } }; - // Show password visiblity hover icon on woocommerce forms + // Show password visibility hover icon on woocommerce forms $( '.woocommerce form .woocommerce-Input[type="password"]' ).wrap( '' ); // Add 'password-input' class to the password wrapper in checkout page. $( '.woocommerce form input' ).filter(':password').parent('span').addClass('password-input'); diff --git a/plugins/woocommerce/includes/abstracts/abstract-wc-order.php b/plugins/woocommerce/includes/abstracts/abstract-wc-order.php index 2bcad8535db..7ba29926df4 100644 --- a/plugins/woocommerce/includes/abstracts/abstract-wc-order.php +++ b/plugins/woocommerce/includes/abstracts/abstract-wc-order.php @@ -134,7 +134,7 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order { /** * This method overwrites the base class's clone method to make it a no-op. In base class WC_Data, we are unsetting the meta_id to clone. * It seems like this was done to avoid conflicting the metadata when duplicating products. However, doing that does not seems necessary for orders. - * In-fact, when we do that for orders, we lose the capability to clone orders with custom meta data by caching plugins. This is because, when we clone an order object for caching, it will clone the metadata without the ID. Unfortunately, when this cached object with nulled meta ID is retreived, WC_Data will consider it as a new meta and will insert it as a new meta-data causing duplicates. + * In-fact, when we do that for orders, we lose the capability to clone orders with custom meta data by caching plugins. This is because, when we clone an order object for caching, it will clone the metadata without the ID. Unfortunately, when this cached object with nulled meta ID is retrieved, WC_Data will consider it as a new meta and will insert it as a new meta-data causing duplicates. * * Eventually, we should move away from overwriting the __clone method in base class itself, since it's easily possible to still duplicate the product without having to hook into the __clone method. * diff --git a/plugins/woocommerce/includes/abstracts/abstract-wc-product.php b/plugins/woocommerce/includes/abstracts/abstract-wc-product.php index 69e4d1f46c6..ac1eb1147ea 100644 --- a/plugins/woocommerce/includes/abstracts/abstract-wc-product.php +++ b/plugins/woocommerce/includes/abstracts/abstract-wc-product.php @@ -1110,7 +1110,7 @@ class WC_Product extends WC_Abstract_Legacy_Product { * position - integer sort order. * visible - If visible on frontend. * variation - If used for variations. - * Indexed by unqiue key to allow clearing old ones after a set. + * Indexed by unique key to allow clearing old ones after a set. * * @since 3.0.0 * @param array $raw_attributes Array of WC_Product_Attribute objects. diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-addons.php b/plugins/woocommerce/includes/admin/class-wc-admin-addons.php index 25dbc15936e..81e3a9f0b84 100644 --- a/plugins/woocommerce/includes/admin/class-wc-admin-addons.php +++ b/plugins/woocommerce/includes/admin/class-wc-admin-addons.php @@ -1119,7 +1119,7 @@ class WC_Admin_Addons { /** * Install WooCommerce Payments from the Extensions screens. * - * @param string $section Optional. Extenstions tab. + * @param string $section Optional. Extensions tab. * * @return void */ diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-status.php b/plugins/woocommerce/includes/admin/class-wc-admin-status.php index 6e92ff6e4ff..0be2542485e 100644 --- a/plugins/woocommerce/includes/admin/class-wc-admin-status.php +++ b/plugins/woocommerce/includes/admin/class-wc-admin-status.php @@ -362,7 +362,7 @@ class WC_Admin_Status { Check again.', 'woocommerce' ), esc_html( implode( ', ', $missing_tables ) ), wp_nonce_url( admin_url( 'admin.php?page=wc-status&tab=tools&action=verify_db_tables' ), 'debug_action' ) @@ -32,7 +32,7 @@ defined( 'ABSPATH' ) || exit; } else { echo wp_kses_post( sprintf( - /* translators: %1%s: Missing tables (seperated by ",") */ + /* translators: %1%s: Missing tables (separated by ",") */ __( 'One or more tables required for WooCommerce to function are missing, some features may not work as expected. Missing tables: %1$s.', 'woocommerce' ), esc_html( implode( ', ', $missing_tables ) ) ) diff --git a/plugins/woocommerce/includes/class-wc-countries.php b/plugins/woocommerce/includes/class-wc-countries.php index f4fa5ceab6e..7a880796eb2 100644 --- a/plugins/woocommerce/includes/class-wc-countries.php +++ b/plugins/woocommerce/includes/class-wc-countries.php @@ -1573,7 +1573,7 @@ class WC_Countries { // Default Locale Can be filtered to override fields in get_address_fields(). Countries with no specific locale will use default. $this->locale['default'] = apply_filters( 'woocommerce_get_country_locale_default', $this->get_default_address_fields() ); - // Filter default AND shop base locales to allow overides via a single function. These will be used when changing countries on the checkout. + // Filter default AND shop base locales to allow overrides via a single function. These will be used when changing countries on the checkout. if ( ! isset( $this->locale[ $this->get_base_country() ] ) ) { $this->locale[ $this->get_base_country() ] = $this->locale['default']; } diff --git a/plugins/woocommerce/includes/class-wc-coupon.php b/plugins/woocommerce/includes/class-wc-coupon.php index 646c7bf0159..48d8f75337f 100644 --- a/plugins/woocommerce/includes/class-wc-coupon.php +++ b/plugins/woocommerce/includes/class-wc-coupon.php @@ -698,7 +698,7 @@ class WC_Coupon extends WC_Legacy_Coupon { * Set the minimum spend amount. * * @since 3.0.0 - * @param float $amount Minium amount. + * @param float $amount Minimum amount. */ public function set_minimum_amount( $amount ) { $this->set_prop( 'minimum_amount', wc_format_decimal( $amount ) ); diff --git a/plugins/woocommerce/includes/class-wc-frontend-scripts.php b/plugins/woocommerce/includes/class-wc-frontend-scripts.php index 4ddbb076061..f67641a2214 100644 --- a/plugins/woocommerce/includes/class-wc-frontend-scripts.php +++ b/plugins/woocommerce/includes/class-wc-frontend-scripts.php @@ -62,7 +62,7 @@ class WC_Frontend_Scripts { * * @since 2.1.0 * @param array List of default WooCommerce styles. - * @retrun array List of styles to enqueue. + * @return array List of styles to enqueue. */ $styles = apply_filters( 'woocommerce_enqueue_styles', diff --git a/plugins/woocommerce/includes/class-wc-post-types.php b/plugins/woocommerce/includes/class-wc-post-types.php index 03d0f5535ea..bb26ec91e1c 100644 --- a/plugins/woocommerce/includes/class-wc-post-types.php +++ b/plugins/woocommerce/includes/class-wc-post-types.php @@ -404,7 +404,7 @@ class WC_Post_Types { ), array( array( - 'woocommerce/product-pricing-field', + 'woocommerce/product-regular-price-field', array( 'name' => 'regular_price', 'label' => __( 'List price', 'woocommerce' ), @@ -420,9 +420,8 @@ class WC_Post_Types { ), array( array( - 'woocommerce/product-pricing-field', + 'woocommerce/product-sale-price-field', array( - 'name' => 'sale_price', 'label' => __( 'Sale price', 'woocommerce' ), ), ), @@ -541,7 +540,7 @@ class WC_Post_Types { ), array( array( - 'woocommerce/product-pricing-field', + 'woocommerce/product-regular-price-field', array( 'name' => 'regular_price', 'label' => __( 'List price', 'woocommerce' ), @@ -556,9 +555,8 @@ class WC_Post_Types { ), array( array( - 'woocommerce/product-pricing-field', + 'woocommerce/product-sale-price-field', array( - 'name' => 'sale_price', 'label' => __( 'Sale price', 'woocommerce' ), ), ), diff --git a/plugins/woocommerce/includes/class-wc-query.php b/plugins/woocommerce/includes/class-wc-query.php index 82d190e0ce8..fc7a9ec95a5 100644 --- a/plugins/woocommerce/includes/class-wc-query.php +++ b/plugins/woocommerce/includes/class-wc-query.php @@ -538,7 +538,7 @@ class WC_Query { // Store reference to this query. self::$product_query = $q; - // Additonal hooks to change WP Query. + // Additional hooks to change WP Query. self::add_filter( 'posts_clauses', array( $this, 'product_query_post_clauses' ), 10, 2 ); add_filter( 'the_posts', array( $this, 'handle_get_posts' ), 10, 2 ); diff --git a/plugins/woocommerce/includes/class-wc-regenerate-images.php b/plugins/woocommerce/includes/class-wc-regenerate-images.php index ca59231e7fb..35f8cde9b2e 100644 --- a/plugins/woocommerce/includes/class-wc-regenerate-images.php +++ b/plugins/woocommerce/includes/class-wc-regenerate-images.php @@ -51,7 +51,7 @@ class WC_Regenerate_Images { add_action( 'admin_init', array( __CLASS__, 'regenerating_notice' ) ); add_action( 'woocommerce_hide_regenerating_thumbnails_notice', array( __CLASS__, 'dismiss_regenerating_notice' ) ); - // Regenerate thumbnails in the background after settings changes. Not ran on multisite to avoid multiple simultanious jobs. + // Regenerate thumbnails in the background after settings changes. Not ran on multisite to avoid multiple simultaneous jobs. if ( ! is_multisite() ) { add_action( 'customize_save_after', array( __CLASS__, 'maybe_regenerate_images' ) ); add_action( 'after_switch_theme', array( __CLASS__, 'maybe_regenerate_images' ) ); diff --git a/plugins/woocommerce/includes/data-stores/abstract-wc-order-data-store-cpt.php b/plugins/woocommerce/includes/data-stores/abstract-wc-order-data-store-cpt.php index 6d29543da62..697edf02c24 100644 --- a/plugins/woocommerce/includes/data-stores/abstract-wc-order-data-store-cpt.php +++ b/plugins/woocommerce/includes/data-stores/abstract-wc-order-data-store-cpt.php @@ -599,7 +599,7 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme } /** - * Helper method to update order metadata from intialized order object. + * Helper method to update order metadata from initialized order object. * * @param WC_Abstract_Order $order Order object. */ diff --git a/plugins/woocommerce/includes/data-stores/class-wc-product-data-store-cpt.php b/plugins/woocommerce/includes/data-stores/class-wc-product-data-store-cpt.php index 6e780300bd4..8eb78696f6e 100644 --- a/plugins/woocommerce/includes/data-stores/class-wc-product-data-store-cpt.php +++ b/plugins/woocommerce/includes/data-stores/class-wc-product-data-store-cpt.php @@ -1137,7 +1137,7 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da /** * Check each variation to find the one that matches the $match_attributes. * - * Note: Not all meta fields will be set which is why we check existance. + * Note: Not all meta fields will be set which is why we check existence. */ foreach ( $sorted_meta as $variation_id => $variation ) { $match = true; diff --git a/plugins/woocommerce/includes/export/class-wc-product-csv-exporter.php b/plugins/woocommerce/includes/export/class-wc-product-csv-exporter.php index a59c6f5ea8c..1c0b976dddf 100644 --- a/plugins/woocommerce/includes/export/class-wc-product-csv-exporter.php +++ b/plugins/woocommerce/includes/export/class-wc-product-csv-exporter.php @@ -182,7 +182,7 @@ class WC_Product_CSV_Exporter extends WC_CSV_Batch_Exporter { $variable_products = array(); foreach ( $products->products as $product ) { - // Check if the category is set, this means we need to fetch variations seperately as they are not tied to a category. + // Check if the category is set, this means we need to fetch variations separately as they are not tied to a category. if ( ! empty( $args['category'] ) && $product->is_type( 'variable' ) ) { $variable_products[] = $product->get_id(); } diff --git a/plugins/woocommerce/includes/shortcodes/class-wc-shortcode-products.php b/plugins/woocommerce/includes/shortcodes/class-wc-shortcode-products.php index 25ac8223eda..9b551b3f2e0 100644 --- a/plugins/woocommerce/includes/shortcodes/class-wc-shortcode-products.php +++ b/plugins/woocommerce/includes/shortcodes/class-wc-shortcode-products.php @@ -653,7 +653,7 @@ class WC_Shortcode_Products { $GLOBALS['post'] = get_post( $product_id ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited setup_postdata( $GLOBALS['post'] ); - // Set custom product visibility when quering hidden products. + // Set custom product visibility when querying hidden products. add_action( 'woocommerce_product_is_visible', array( $this, 'set_product_as_visible' ) ); // Render product template. diff --git a/plugins/woocommerce/includes/tracks/events/class-wc-settings-tracking.php b/plugins/woocommerce/includes/tracks/events/class-wc-settings-tracking.php index 46803b1d13a..970ca98f723 100644 --- a/plugins/woocommerce/includes/tracks/events/class-wc-settings-tracking.php +++ b/plugins/woocommerce/includes/tracks/events/class-wc-settings-tracking.php @@ -28,6 +28,21 @@ class WC_Settings_Tracking { */ protected $updated_options = array(); + /** + * List of option names that are dropdown menus. + * + * @var array + */ + protected $dropdown_menu_options = array(); + + + /** + * List of options that have been modified. + * + * @var array + */ + protected $modified_options = array(); + /** * Toggled options. * @@ -61,6 +76,10 @@ class WC_Settings_Tracking { public function add_option_to_list( $option ) { $this->allowed_options[] = $option['id']; + if ( isset( $option['options'] ) ) { + $this->dropdown_menu_options[] = $option['id']; + } + // Delay attaching this action since it could get fired a lot. if ( false === has_action( 'update_option', array( $this, 'track_setting_change' ) ) ) { add_action( 'update_option', array( $this, 'track_setting_change' ), 10, 3 ); @@ -91,8 +110,10 @@ class WC_Settings_Tracking { return; } - // Check and save toggled options. - if ( in_array( $new_value, array( 'yes', 'no' ), true ) && in_array( $old_value, array( 'yes', 'no' ), true ) ) { + if ( in_array( $option_name, $this->dropdown_menu_options, true ) ) { + $this->modified_options[ $option_name ] = $new_value; + } elseif ( in_array( $new_value, array( 'yes', 'no' ), true ) && in_array( $old_value, array( 'yes', 'no' ), true ) ) { + // Save toggled options. $option_state = 'yes' === $new_value ? 'enabled' : 'disabled'; $this->toggled_options[ $option_state ][] = $option_name; } @@ -120,6 +141,12 @@ class WC_Settings_Tracking { } } + if ( ! empty( $this->modified_options ) ) { + foreach ( $this->modified_options as $option_name => $selected_option ) { + $properties[ $option_name ] = $selected_option ?? ''; + } + } + $properties['tab'] = $current_tab ?? ''; $properties['section'] = $current_section ?? ''; diff --git a/plugins/woocommerce/includes/wc-account-functions.php b/plugins/woocommerce/includes/wc-account-functions.php index 531176e7279..7cefe49d9eb 100644 --- a/plugins/woocommerce/includes/wc-account-functions.php +++ b/plugins/woocommerce/includes/wc-account-functions.php @@ -102,7 +102,7 @@ function wc_get_account_menu_items() { 'edit-address' => _n( 'Address', 'Addresses', ( 1 + (int) wc_shipping_enabled() ), 'woocommerce' ), 'payment-methods' => __( 'Payment methods', 'woocommerce' ), 'edit-account' => __( 'Account details', 'woocommerce' ), - 'customer-logout' => __( 'Logout', 'woocommerce' ), + 'customer-logout' => __( 'Log out', 'woocommerce' ), ); // Remove missing endpoints. diff --git a/plugins/woocommerce/includes/wc-product-functions.php b/plugins/woocommerce/includes/wc-product-functions.php index f27291d3ca9..da041dbb0ea 100644 --- a/plugins/woocommerce/includes/wc-product-functions.php +++ b/plugins/woocommerce/includes/wc-product-functions.php @@ -698,7 +698,7 @@ function wc_get_product_variation_attributes( $variation_id ) { $attribute = 'attribute_' . sanitize_title( $attribute_name ); $found_parent_attributes[] = $attribute; if ( ! array_key_exists( $attribute, $variation_attributes ) ) { - $variation_attributes[ $attribute ] = ''; // Add it - 'any' will be asumed. + $variation_attributes[ $attribute ] = ''; // Add it - 'any' will be assumed. } } } diff --git a/plugins/woocommerce/includes/wc-template-functions.php b/plugins/woocommerce/includes/wc-template-functions.php index 9429164250d..31db16b2880 100644 --- a/plugins/woocommerce/includes/wc-template-functions.php +++ b/plugins/woocommerce/includes/wc-template-functions.php @@ -817,7 +817,7 @@ function wc_privacy_policy_page_id() { } /** - * See if the checkbox is enabled or not based on the existance of the terms page and checkbox text. + * See if the checkbox is enabled or not based on the existence of the terms page and checkbox text. * * @since 3.4.0 * @return bool @@ -3549,7 +3549,7 @@ function wc_display_product_attributes( $product ) { * Hook: woocommerce_display_product_attributes. * * @since 3.6.0. - * @param array $product_attributes Array of atributes to display; label, value. + * @param array $product_attributes Array of attributes to display; label, value. * @param WC_Product $product Showing attributes for this product. */ $product_attributes = apply_filters( 'woocommerce_display_product_attributes', $product_attributes, $product ); diff --git a/plugins/woocommerce/includes/wc-term-functions.php b/plugins/woocommerce/includes/wc-term-functions.php index 3cbc7032a81..fbe13b3f5f6 100644 --- a/plugins/woocommerce/includes/wc-term-functions.php +++ b/plugins/woocommerce/includes/wc-term-functions.php @@ -295,7 +295,7 @@ add_action( 'wp_upgrade', 'wc_taxonomy_metadata_migrate_data', 10, 2 ); * * @param int $the_term Term ID. * @param int $next_id The id of the next sibling element in save hierarchy level. - * @param string $taxonomy Taxnomy. + * @param string $taxonomy Taxonomy. * @param int $index Term index (default: 0). * @param mixed $terms List of terms. (default: null). * @return int @@ -594,7 +594,7 @@ function wc_clear_term_product_ids( $object_id, $terms, $tt_ids, $taxonomy, $app add_action( 'set_object_terms', 'wc_clear_term_product_ids', 10, 6 ); /** - * Get full list of product visibilty term ids. + * Get full list of product visibility term ids. * * @since 3.0.0 * @return int[] diff --git a/plugins/woocommerce/includes/wc-update-functions.php b/plugins/woocommerce/includes/wc-update-functions.php index d70a4233816..3aa0b6a0246 100644 --- a/plugins/woocommerce/includes/wc-update-functions.php +++ b/plugins/woocommerce/includes/wc-update-functions.php @@ -1902,7 +1902,7 @@ function wc_update_350_db_version() { } /** - * Drop the fk_wc_download_log_permission_id FK as we use a new one with the table and blog prefix for MS compatability. + * Drop the fk_wc_download_log_permission_id FK as we use a new one with the table and blog prefix for MS compatibility. * * @return void */ diff --git a/plugins/woocommerce/src/Admin/API/Init.php b/plugins/woocommerce/src/Admin/API/Init.php index d0e6197ec7e..338bda5277d 100644 --- a/plugins/woocommerce/src/Admin/API/Init.php +++ b/plugins/woocommerce/src/Admin/API/Init.php @@ -37,7 +37,7 @@ class Init { } /** - * Boostrap REST API. + * Bootstrap REST API. */ public function __construct() { // Hook in data stores. diff --git a/plugins/woocommerce/src/Admin/API/ProductVariations.php b/plugins/woocommerce/src/Admin/API/ProductVariations.php index a5307500a9b..aaf69ac5b2e 100644 --- a/plugins/woocommerce/src/Admin/API/ProductVariations.php +++ b/plugins/woocommerce/src/Admin/API/ProductVariations.php @@ -129,7 +129,7 @@ class ProductVariations extends \WC_REST_Product_Variations_Controller { unset( $args['s'] ); } - // Retreive variations without specifying a parent product. + // Retrieve variations without specifying a parent product. if ( "/{$this->namespace}/variations" === $request->get_route() ) { unset( $args['post_parent'] ); } diff --git a/plugins/woocommerce/src/Admin/API/Products.php b/plugins/woocommerce/src/Admin/API/Products.php index 2e9e16c7aab..e4353df76ce 100644 --- a/plugins/woocommerce/src/Admin/API/Products.php +++ b/plugins/woocommerce/src/Admin/API/Products.php @@ -142,7 +142,7 @@ class Products extends \WC_REST_Products_Controller { /** * Check whether the request is for products low in stock. * - * It matches requests with paramaters: + * It matches requests with parameters: * * low_in_stock = true * page = 1 diff --git a/plugins/woocommerce/src/Admin/Features/AsyncProductEditorCategoryField/Init.php b/plugins/woocommerce/src/Admin/Features/AsyncProductEditorCategoryField/Init.php index a33411d3dee..89b53f0ac00 100644 --- a/plugins/woocommerce/src/Admin/Features/AsyncProductEditorCategoryField/Init.php +++ b/plugins/woocommerce/src/Admin/Features/AsyncProductEditorCategoryField/Init.php @@ -46,9 +46,10 @@ class Init { * Enqueue scripts needed for the product form block editor. */ public function enqueue_scripts() { - if ( ! PageController::is_admin_or_embed_page() ) { + if ( ! PageController::is_embed_page() ) { return; } + WCAdminAssets::register_script( 'wp-admin-scripts', 'product-category-metabox', true ); wp_localize_script( 'wc-admin-product-category-metabox', @@ -66,7 +67,7 @@ class Init { * Enqueue styles needed for the rich text editor. */ public function enqueue_styles() { - if ( ! PageController::is_admin_or_embed_page() ) { + if ( ! PageController::is_embed_page() ) { return; } $version = Constants::get_constant( 'WC_VERSION' ); diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/DeprecatedExtendedTask.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/DeprecatedExtendedTask.php index c8375dc4ab1..f2e62fc96b7 100644 --- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/DeprecatedExtendedTask.php +++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/DeprecatedExtendedTask.php @@ -76,7 +76,7 @@ class DeprecatedExtendedTask extends Task { } /** - * Additonal info. + * Additional info. * * @return string */ diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskLists.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskLists.php index a2510fd6ed6..e3e2f91a72a 100644 --- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskLists.php +++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskLists.php @@ -253,7 +253,7 @@ class TaskLists { } /** - * Temporarily store the active task to persist across page loads when neccessary. + * Temporarily store the active task to persist across page loads when necessary. * Most tasks do not need this. */ public static function set_active_task() { diff --git a/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/BlockRegistry.php b/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/BlockRegistry.php index e0c848166ef..3baf0db8e51 100644 --- a/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/BlockRegistry.php +++ b/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/BlockRegistry.php @@ -31,6 +31,8 @@ class BlockRegistry { 'woocommerce/product-name-field', 'woocommerce/product-pricing-field', 'woocommerce/product-radio-field', + 'woocommerce/product-regular-price-field', + 'woocommerce/product-sale-price-field', 'woocommerce/product-schedule-sale-fields', 'woocommerce/product-section', 'woocommerce/product-shipping-dimensions-fields', diff --git a/plugins/woocommerce/src/Internal/Admin/Loader.php b/plugins/woocommerce/src/Internal/Admin/Loader.php index 27758986e18..e190836b0cb 100644 --- a/plugins/woocommerce/src/Internal/Admin/Loader.php +++ b/plugins/woocommerce/src/Internal/Admin/Loader.php @@ -302,7 +302,7 @@ class Loader { } /** - * Hooks extra neccessary data into the component settings array already set in WooCommerce core. + * Hooks extra necessary data into the component settings array already set in WooCommerce core. * * @param array $settings Array of component settings. * @return array Array of component settings. diff --git a/plugins/woocommerce/src/Internal/Admin/Settings.php b/plugins/woocommerce/src/Internal/Admin/Settings.php index 516fb1f294b..27d4b721384 100644 --- a/plugins/woocommerce/src/Internal/Admin/Settings.php +++ b/plugins/woocommerce/src/Internal/Admin/Settings.php @@ -105,7 +105,7 @@ class Settings { } /** - * Hooks extra neccessary data into the component settings array already set in WooCommerce core. + * Hooks extra necessary data into the component settings array already set in WooCommerce core. * * @param array $settings Array of component settings. * @return array Array of component settings. diff --git a/plugins/woocommerce/src/Internal/Admin/WCAdminAssets.php b/plugins/woocommerce/src/Internal/Admin/WCAdminAssets.php index 59f5cd9db59..0d44957db99 100644 --- a/plugins/woocommerce/src/Internal/Admin/WCAdminAssets.php +++ b/plugins/woocommerce/src/Internal/Admin/WCAdminAssets.php @@ -238,7 +238,7 @@ class WCAdminAssets { } /** - * Registers all the neccessary scripts and styles to show the admin experience. + * Registers all the necessary scripts and styles to show the admin experience. */ public function register_scripts() { if ( ! function_exists( 'wp_set_script_translations' ) ) { diff --git a/plugins/woocommerce/src/Internal/Admin/WCAdminUser.php b/plugins/woocommerce/src/Internal/Admin/WCAdminUser.php index 748f4b96547..76897a8cf24 100644 --- a/plugins/woocommerce/src/Internal/Admin/WCAdminUser.php +++ b/plugins/woocommerce/src/Internal/Admin/WCAdminUser.php @@ -124,7 +124,7 @@ class WCAdminUser { } /** - * Helper to retrive user data fields. + * Helper to retrieve user data fields. * * Migrates old key prefixes as well. * diff --git a/plugins/woocommerce/tests/Tools/CodeHacking/CodeHacker.php b/plugins/woocommerce/tests/Tools/CodeHacking/CodeHacker.php index 9b18bbe214c..94669c2a8e0 100644 --- a/plugins/woocommerce/tests/Tools/CodeHacking/CodeHacker.php +++ b/plugins/woocommerce/tests/Tools/CodeHacking/CodeHacker.php @@ -466,7 +466,7 @@ final class CodeHacker { } /** - * Apply the reigstered hacks to the contents of a file. + * Apply the registered hacks to the contents of a file. * * @param string $code Code content to hack. * @param string $path Path of the file being hacked. diff --git a/plugins/woocommerce/tests/Tools/CodeHacking/Hacks/FunctionsMockerHack.php b/plugins/woocommerce/tests/Tools/CodeHacking/Hacks/FunctionsMockerHack.php index cf3fc83c6ab..b37edc6ae77 100644 --- a/plugins/woocommerce/tests/Tools/CodeHacking/Hacks/FunctionsMockerHack.php +++ b/plugins/woocommerce/tests/Tools/CodeHacking/Hacks/FunctionsMockerHack.php @@ -86,7 +86,7 @@ final class FunctionsMockerHack extends CodeHack { } /** - * Hacks code by replacing elegible function invocations with an invocation to this class' static method with the same name. + * Hacks code by replacing eligible function invocations with an invocation to this class' static method with the same name. * * @param string $code The code to hack. * @param string $path The path of the file containing the code to hack. diff --git a/plugins/woocommerce/tests/Tools/CodeHacking/Hacks/StaticMockerHack.php b/plugins/woocommerce/tests/Tools/CodeHacking/Hacks/StaticMockerHack.php index 65b5b334aca..034eac10824 100644 --- a/plugins/woocommerce/tests/Tools/CodeHacking/Hacks/StaticMockerHack.php +++ b/plugins/woocommerce/tests/Tools/CodeHacking/Hacks/StaticMockerHack.php @@ -72,7 +72,7 @@ final class StaticMockerHack extends CodeHack { } /** - * Hacks code by replacing elegible method invocations with an invocation a static method on this class composed from the class and the method names. + * Hacks code by replacing eligible method invocations with an invocation a static method on this class composed from the class and the method names. * * @param string $code The code to hack. * @param string $path The path of the file containing the code to hack. diff --git a/plugins/woocommerce/tests/Tools/CodeHacking/README.md b/plugins/woocommerce/tests/Tools/CodeHacking/README.md index f4beef28b68..bcb657fc1d2 100644 --- a/plugins/woocommerce/tests/Tools/CodeHacking/README.md +++ b/plugins/woocommerce/tests/Tools/CodeHacking/README.md @@ -125,7 +125,7 @@ StaticMockerHack::add_method_mocks([ Inside your test files you can create classes that extend classes marked as `final` thanks to the `BypassFinalsHack` that is registered at bootstrap time. No extra configuration is needed. -If you want to try it out, mark the `WC_Admin_Foobar` in the previos example as `final`, then add the following to the tests file: `class WC_Admin_Foobar_Subclass extends WC_Admin_Foobar {}`. Without the hack you would get a `Class WC_Admin_Foobar_Subclass may not inherit from final class (WC_Admin_Foobar)` error when trying to run the tests. +If you want to try it out, mark the `WC_Admin_Foobar` in the previous example as `final`, then add the following to the tests file: `class WC_Admin_Foobar_Subclass extends WC_Admin_Foobar {}`. Without the hack you would get a `Class WC_Admin_Foobar_Subclass may not inherit from final class (WC_Admin_Foobar)` error when trying to run the tests. ## How it works under the hood diff --git a/plugins/woocommerce/tests/api-core-tests/tests/payment-gateways/payment-gateways-crud.test.js b/plugins/woocommerce/tests/api-core-tests/tests/payment-gateways/payment-gateways-crud.test.js index cdc7509c080..8f5f09d45ae 100644 --- a/plugins/woocommerce/tests/api-core-tests/tests/payment-gateways/payment-gateways-crud.test.js +++ b/plugins/woocommerce/tests/api-core-tests/tests/payment-gateways/payment-gateways-crud.test.js @@ -12,7 +12,7 @@ const { */ test.describe('Payment Gateways API tests', () => { - test('can view all payment gatways', async ({ + test('can view all payment gateways', async ({ request }) => { // call API to retrieve the payment gateways @@ -167,7 +167,7 @@ test.describe('Payment Gateways API tests', () => { test('can view a payment gateway', async ({ request }) => { - // call API to retrieve a single payment gatway + // call API to retrieve a single payment gateway const response = await request.get('/wp-json/wc/v3/payment_gateways/bacs'); const responseJSON = await response.json(); expect(response.status()).toEqual(200); @@ -213,7 +213,7 @@ test.describe('Payment Gateways API tests', () => { test('can update a payment gateway', async ({ request }) => { - // call API to update a payment gatway + // call API to update a payment gateway const response = await request.put('/wp-json/wc/v3/payment_gateways/bacs', { data: { enabled: true diff --git a/plugins/woocommerce/tests/api-core-tests/tests/products/products-crud.test.js b/plugins/woocommerce/tests/api-core-tests/tests/products/products-crud.test.js index 00fcd5b58d7..6d3a2e8afd0 100644 --- a/plugins/woocommerce/tests/api-core-tests/tests/products/products-crud.test.js +++ b/plugins/woocommerce/tests/api-core-tests/tests/products/products-crud.test.js @@ -558,7 +558,7 @@ test.describe('Products API tests: CRUD', () => { const response = await request.post('wp-json/wc/v3/products/reviews', { data: { product_id: 999, - review: "A non existant product!", + review: "A non existent product!", reviewer: "John Do Not", reviewer_email: "john.do.not@example.com", rating: 5 diff --git a/plugins/woocommerce/tests/api-core-tests/tests/settings/settings-crud.test.js b/plugins/woocommerce/tests/api-core-tests/tests/settings/settings-crud.test.js index f5314972d76..238a4c552c0 100644 --- a/plugins/woocommerce/tests/api-core-tests/tests/settings/settings-crud.test.js +++ b/plugins/woocommerce/tests/api-core-tests/tests/settings/settings-crud.test.js @@ -1968,7 +1968,7 @@ test.describe('Settings API tests: CRUD', () => { test.describe('List all Email Customer Processing Order settings', () => { - test('can retrieve all email customer processsing order settings', async ({ + test('can retrieve all email customer processing order settings', async ({ request }) => { // call API to retrieve all settings options diff --git a/plugins/woocommerce/tests/api-core-tests/tests/shipping/shipping-zones.test.js b/plugins/woocommerce/tests/api-core-tests/tests/shipping/shipping-zones.test.js index ef948dea1c4..af6cb93e4ce 100644 --- a/plugins/woocommerce/tests/api-core-tests/tests/shipping/shipping-zones.test.js +++ b/plugins/woocommerce/tests/api-core-tests/tests/shipping/shipping-zones.test.js @@ -78,7 +78,7 @@ test.describe( 'Shipping zones API tests', () => { } ); test( 'can retrieve a shipping zone', async ({request}) => { - //call API to retrive the created shipping zone + //call API to retrieve the created shipping zone const response = await request.get( `/wp-json/wc/v3/shipping/zones/${shippingZone.id}`); const responseJSON = await response.json(); @@ -88,7 +88,7 @@ test.describe( 'Shipping zones API tests', () => { } ); test( 'can list all shipping zones', async ({request}) => { - //call API to retrive all the shipping zones + //call API to retrieve all the shipping zones const response = await request.get( 'wp-json/wc/v3/shipping/zones'); const responseJSON = await response.json(); @@ -119,7 +119,7 @@ test.describe( 'Shipping zones API tests', () => { test( 'can add a shipping region to a shipping zone', async ({request}) => { - //call API to retrive the locations of the last created shipping zone + //call API to retrieve the locations of the last created shipping zone const response = await request.get( `/wp-json/wc/v3/shipping/zones/${shippingZone.id}/locations`); expect( response.status() ).toEqual( 200 ); diff --git a/plugins/woocommerce/tests/api-core-tests/tests/system-status/system-status-crud.test.js b/plugins/woocommerce/tests/api-core-tests/tests/system-status/system-status-crud.test.js index 4518718914c..28f30c6f45d 100644 --- a/plugins/woocommerce/tests/api-core-tests/tests/system-status/system-status-crud.test.js +++ b/plugins/woocommerce/tests/api-core-tests/tests/system-status/system-status-crud.test.js @@ -550,7 +550,7 @@ test.describe('System Status API tests', () => { test('can retrieve a system status tool', async ({ request }) => { - // call API to retrieve a system staus tool + // call API to retrieve a system status tool const response = await request.get('/wp-json/wc/v3/system_status/tools/clear_transients'); const responseJSON = await response.json(); expect(response.status()).toEqual(200); diff --git a/plugins/woocommerce/tests/e2e-pw/README.md b/plugins/woocommerce/tests/e2e-pw/README.md index 51e62700679..93acf0a7fed 100644 --- a/plugins/woocommerce/tests/e2e-pw/README.md +++ b/plugins/woocommerce/tests/e2e-pw/README.md @@ -74,7 +74,7 @@ If you would like to customize the `PHP`, `WordPress` or `WooCommerce` versions - `WC_VERSION` - Acceptable versions can be found on the [WooCommerce Releases](https://github.com/woocommerce/woocommerce/releases) page - `PHP` - - Any PHP version you see it. Please note that WooCommerce requries a minimum of PHP 7.2. + - Any PHP version you see it. Please note that WooCommerce requires a minimum of PHP 7.2. **Example** @@ -82,7 +82,7 @@ The command below will create and environment with WordPress version 6.2, WooCom `UPDATE_WP_JSON_FILE=1 WP_VERSION=6.2 WC_TEST_VERSION=7.5.1 PHP_VERSION=8.2 pnpm run env:test` -If you'd like to run with the default configuation, simply remove the `UPDATE_WP_JSON_FILE`. +If you'd like to run with the default configuration, simply remove the `UPDATE_WP_JSON_FILE`. For more information how to configure the test environment for `wp-env`, please checkout the [documentation](https://github.com/WordPress/gutenberg/tree/trunk/packages/env) documentation. diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-post.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-post.spec.js new file mode 100644 index 00000000000..8b059e56e95 --- /dev/null +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-post.spec.js @@ -0,0 +1,74 @@ +const { test, expect, request } = require( '@playwright/test' ); +const { admin } = require( '../../test-data/data' ); + +const postTitle = `Post-${ new Date().getTime().toString() }`; + +test.describe( 'Can create a new post', () => { + test.use( { storageState: process.env.ADMINSTATE } ); + + test.afterAll( async ( { baseURL } ) => { + const base64auth = Buffer.from( + `${ admin.username }:${ admin.password }` + ).toString( 'base64' ); + const wpApi = await request.newContext( { + baseURL: `${ baseURL }/wp-json/wp/v2/`, + extraHTTPHeaders: { + Authorization: `Basic ${ base64auth }`, + }, + } ); + + let response = await wpApi.get( `posts` ); + const allPosts = await response.json(); + + await allPosts.forEach( async ( post ) => { + if ( post.title.rendered === postTitle ) { + response = await wpApi.delete( `posts/${ post.id }`, { + data: { + force: true, + }, + } ); + expect( response.ok() ).toBeTruthy(); + } + } ); + } ); + + test( 'can create new post', async ( { page } ) => { + await page.goto( 'wp-admin/post-new.php' ); + + const welcomeModalVisible = await page + .getByRole( 'heading', { + name: 'Welcome to the block editor', + } ) + .isVisible(); + + if ( welcomeModalVisible ) { + await page.getByRole( 'button', { name: 'Close dialog' } ).click(); + } + + await page + .getByRole( 'textbox', { name: 'Add Title' } ) + .fill( postTitle ); + + await page.getByRole( 'button', { name: 'Add default block' } ).click(); + + await page + .getByRole( 'document', { + name: + 'Empty block; start writing or type forward slash to choose a block', + } ) + .fill( 'Test Post' ); + + await page + .getByRole( 'button', { name: 'Publish', exact: true } ) + .click(); + + await page + .getByRole( 'region', { name: 'Editor publish' } ) + .getByRole( 'button', { name: 'Publish', exact: true } ) + .click(); + + await expect( + page.getByText( `${ postTitle } is now live.` ) + ).toBeVisible(); + } ); +} ); diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/page-loads.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/page-loads.spec.js index 0093cf09ed8..76ba594c319 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/page-loads.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/page-loads.spec.js @@ -1,6 +1,6 @@ const { test, expect } = require( '@playwright/test' ); -// a represenation of the menu structure for WC +// a representation of the menu structure for WC const wcPages = [ { name: 'WooCommerce', diff --git a/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout-create-account.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout-create-account.spec.js index 856f528a112..69f6c765faf 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout-create-account.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout-create-account.spec.js @@ -169,7 +169,7 @@ test.describe( 'Shopper Checkout Create Account', () => { await expect( page.locator( 'h1.entry-title' ) ).toContainText( 'My account' ); - await page.click( 'text=Logout' ); + await page.click( 'text=Log out' ); // sign in as admin to confirm account creation await page.fill( '#username', admin.username ); await page.fill( '#password', admin.password ); diff --git a/plugins/woocommerce/tests/e2e-pw/tests/shopper/my-account-create-account.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/shopper/my-account-create-account.spec.js index 8364bd746bc..0da54a72912 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/shopper/my-account-create-account.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/shopper/my-account-create-account.spec.js @@ -78,7 +78,7 @@ test.describe( 'Shopper My Account Create Account', () => { await expect( page.locator( 'h1.entry-title' ) ).toContainText( 'My account' ); - await expect( page.locator( 'text=Logout' ) ).toBeVisible(); + await expect( page.locator( 'text=Log out' ).first() ).toBeVisible(); await page.goto( 'my-account/edit-account/' ); await expect( page.locator( '#account_email' ) ).toHaveValue( diff --git a/plugins/woocommerce/tests/e2e-pw/tests/shopper/my-account.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/shopper/my-account.spec.js index 85598a8287f..611b771300a 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/shopper/my-account.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/shopper/my-account.spec.js @@ -45,7 +45,7 @@ test.describe( 'My account page', () => { page.locator( '.woocommerce-MyAccount-navigation-link--customer-logout' ) - ).toContainText( 'Logout' ); + ).toContainText( 'Log out' ); } ); for ( let i = 0; i < pages.length; i++ ) { diff --git a/plugins/woocommerce/tests/e2e-pw/tests/shopper/variable-product-updates.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/shopper/variable-product-updates.spec.js index 9f31e198b8b..869d563228b 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/shopper/variable-product-updates.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/shopper/variable-product-updates.spec.js @@ -174,7 +174,7 @@ test.describe( 'Shopper > Update variable product', () => { ).toContainText( productPrice ); } ); - test( 'Shopper can change attributes to combination with dimentions and weight', async ( { + test( 'Shopper can change attributes to combination with dimensions and weight', async ( { page, } ) => { await page.goto( `product/${ slug }` ); diff --git a/plugins/woocommerce/tests/legacy/unit-tests/account/functions.php b/plugins/woocommerce/tests/legacy/unit-tests/account/functions.php index 98c644c5e9d..b3305063794 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/account/functions.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/account/functions.php @@ -52,7 +52,7 @@ class WC_Tests_Account_Functions extends WC_Unit_Test_Case { 'downloads' => 'Downloads', 'edit-address' => 'Addresses', 'edit-account' => 'Account details', - 'customer-logout' => 'Logout', + 'customer-logout' => 'Log out', ), wc_get_account_menu_items() ); diff --git a/plugins/woocommerce/tests/legacy/unit-tests/cart/cart.php b/plugins/woocommerce/tests/legacy/unit-tests/cart/cart.php index f0e27b38d9f..80cc91f8438 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/cart/cart.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/cart/cart.php @@ -2199,7 +2199,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case { /** * Test that adding a variation via URL parameter succeeds when some attributes belong to the - * variation and others are specificed via URL parameter. + * variation and others are specified via URL parameter. */ public function test_add_variation_by_url_with_valid_attribute() { add_filter( 'woocommerce_add_to_cart_redirect', '__return_false' ); diff --git a/plugins/woocommerce/tests/legacy/unit-tests/coupon/data.php b/plugins/woocommerce/tests/legacy/unit-tests/coupon/data.php index 13f229b97cd..853bff061e0 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/coupon/data.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/coupon/data.php @@ -30,7 +30,7 @@ class WC_Tests_Coupon_Data extends WC_Unit_Test_Case { * @since 3.0.0 */ public function test_coupon_backwards_compat_props_use_correct_getters() { - // Accessing properties directly will throw some wanted deprected notices + // Accessing properties directly will throw some wanted deprecated notices // So we need to let PHPUnit know we are expecting them and it's fine to continue $legacy_keys = array( 'id', diff --git a/plugins/woocommerce/tests/legacy/unit-tests/customer/crud.php b/plugins/woocommerce/tests/legacy/unit-tests/customer/crud.php index 6b09a159d7d..ffc37b660ba 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/customer/crud.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/customer/crud.php @@ -118,7 +118,7 @@ class WC_Tests_CustomerCRUD extends WC_Unit_Test_Case { */ public function test_customer_backwards_compat() { // Properties. - // Accessing properties directly will throw some wanted deprected notices + // Accessing properties directly will throw some wanted deprecated notices // So we need to let PHPUnit know we are expecting them and it's fine to continue $legacy_keys = array( 'id', diff --git a/plugins/woocommerce/tests/legacy/unit-tests/email/emails.php b/plugins/woocommerce/tests/legacy/unit-tests/email/emails.php index 40a6b3a6333..80664a6944b 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/email/emails.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/email/emails.php @@ -51,7 +51,7 @@ class WC_Tests_WC_Emails extends WC_Unit_Test_Case { } /** - * Test that we remove elemets with style display none from html mails. + * Test that we remove elements with style display none from html mails. */ public function test_remove_display_none_elements() { $email = new WC_Email(); diff --git a/plugins/woocommerce/tests/legacy/unit-tests/libraries/class-wc-mock-background-process.php b/plugins/woocommerce/tests/legacy/unit-tests/libraries/class-wc-mock-background-process.php index eab4269c3c7..2cc6da806d9 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/libraries/class-wc-mock-background-process.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/libraries/class-wc-mock-background-process.php @@ -60,7 +60,7 @@ class WC_Mock_Background_Process extends WC_Background_Process { * @return bool */ protected function task( $item ) { - // We sleep for 5 seconds to mimic a long running tast to complete some tests. + // We sleep for 5 seconds to mimic a long running task to complete some tests. sleep( 5 ); update_option( $item['mock_key'], $item['mock_value'] ); return false; diff --git a/plugins/woocommerce/tests/legacy/unit-tests/order/class-wc-tests-orders.php b/plugins/woocommerce/tests/legacy/unit-tests/order/class-wc-tests-orders.php index 9073011470f..9409e2088ed 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/order/class-wc-tests-orders.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/order/class-wc-tests-orders.php @@ -53,7 +53,7 @@ class WC_Tests_Orders extends WC_Unit_Test_Case { } /** - * Test shipping is added and rounded correctly when addedd to total. + * Test shipping is added and rounded correctly when added to total. * * @throws WC_Data_Exception When lines cannot be added to order. */ diff --git a/plugins/woocommerce/tests/legacy/unit-tests/order/coupons.php b/plugins/woocommerce/tests/legacy/unit-tests/order/coupons.php index a5306b1544e..4e7f5d20a70 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/order/coupons.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/order/coupons.php @@ -191,7 +191,7 @@ class WC_Tests_Order_Coupons extends WC_Unit_Test_Case { $this->assertEquals( '799.00', $order->get_total(), $order->get_total() ); /** - * Discount should be based on subtotal unless coupons apply sequencially. + * Discount should be based on subtotal unless coupons apply sequentially. * * Coupon will therefore discount 200. Compare the total without tax so we can compare the ex tax price and avoid rounding mishaps. */ diff --git a/plugins/woocommerce/tests/legacy/unit-tests/product/data-store.php b/plugins/woocommerce/tests/legacy/unit-tests/product/data-store.php index 586392ac3a8..d6359bb91de 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/product/data-store.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/product/data-store.php @@ -681,7 +681,7 @@ class WC_Tests_Product_Data_Store extends WC_Unit_Test_Case { // Check the variation with a multiword attribute name. $this->assertEquals( 'color: Green, mounting-plate: galaxy-s6, support: one-year', $multiword_attribute_variation->get_attribute_summary() ); - // Add atributes to parent so that they are loaded correctly for variation. + // Add attributes to parent so that they are loaded correctly for variation. $attribute_1 = new WC_Product_Attribute(); $attribute_1->set_name( 'color' ); $attribute_1->set_options( array( 'Green', 'Blue' ) ); diff --git a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/payment-gateways.php b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/payment-gateways.php index 1bd00c5c637..8c19b7e27a6 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/payment-gateways.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/payment-gateways.php @@ -247,7 +247,7 @@ class Payment_Gateways extends WC_REST_Unit_Test_Case { $this->assertEquals( 'woo@woo.local', $paypal['settings']['email']['value'] ); $this->assertEquals( 'yes', $paypal['settings']['testmode']['value'] ); - // Test bogus paramter. + // Test bogus parameter. $request = new WP_REST_Request( 'POST', '/wc/v3/payment_gateways/paypal' ); $request->set_body_params( array( diff --git a/plugins/woocommerce/tests/legacy/unit-tests/tax/tax.php b/plugins/woocommerce/tests/legacy/unit-tests/tax/tax.php index 2f09f85235f..f0124d823d2 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/tax/tax.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/tax/tax.php @@ -1,6 +1,6 @@ queue->actions = array(); - // Force a failure by sabotaging the query run after retreiving order coupons. + // Force a failure by sabotaging the query run after retrieving order coupons. add_filter( 'query', array( $this, 'filter_order_query' ) ); // Initiate sync. diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-import.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-import.php index b70e973b83f..b40e02390ea 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-import.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-import.php @@ -85,7 +85,7 @@ class WC_Admin_Tests_API_Reports_Import extends WC_REST_Unit_Test_Case { } /** - * Test the import paramaters. + * Test the import parameters. */ public function test_import_params() { global $wpdb; diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-orders.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-orders.php index 719bd74c02b..19478477f3a 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-orders.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-orders.php @@ -184,8 +184,8 @@ class WC_Admin_Tests_API_Reports_Orders extends WC_REST_Unit_Test_Case { $bad_args = array( 'not an array!', // Not an array. array( 1, 2, 3 ), // Not a tuple. - array( -1, $small_term->term_id ), // Invaid attribute ID. - array( $size_attr_id, -1 ), // Invaid term ID. + array( -1, $small_term->term_id ), // Invalid attribute ID. + array( $size_attr_id, -1 ), // Invalid term ID. ); foreach ( $bad_args as $bad_arg ) { diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/class-wc-tests-shipping-label-banner-display-rules.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/class-wc-tests-shipping-label-banner-display-rules.php index 9744f57fc97..e0daecd2d80 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/class-wc-tests-shipping-label-banner-display-rules.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/class-wc-tests-shipping-label-banner-display-rules.php @@ -20,7 +20,7 @@ class WC_Admin_Tests_Shipping_Label_Banner_Display_Rules extends WC_Unit_Test_Ca private $valid_jetpack_version = '4.4'; /** - * Stores the default WordPress options stored in teh database. + * Stores the default WordPress options stored in the database. * * @var array */ diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/onboarding-tasks/task-list.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/onboarding-tasks/task-list.php index 0d57109a7b8..a7c8e931ae5 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/onboarding-tasks/task-list.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/onboarding-tasks/task-list.php @@ -409,7 +409,7 @@ class WC_Admin_Tests_OnboardingTasks_TaskList extends WC_Unit_Test_Case { } /** - * Test that the list ID is retreived. + * Test that the list ID is retrieved. */ public function test_get_list_id() { $this->assertEquals( 'setup', $this->list->get_list_id() ); diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/onboarding-tasks/task.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/onboarding-tasks/task.php index bebd752a96f..6bfee646306 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/onboarding-tasks/task.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/onboarding-tasks/task.php @@ -296,7 +296,7 @@ class WC_Admin_Tests_OnboardingTasks_Task extends WC_Unit_Test_Case { } /** - * Test that the parent list ID is retreived. + * Test that the parent list ID is retrieved. */ public function test_get_list_id() { $task = new TestTask( diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/onboarding-tasks/test-task.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/onboarding-tasks/test-task.php index 262a9503305..f5c840bdb7a 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/onboarding-tasks/test-task.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/onboarding-tasks/test-task.php @@ -79,7 +79,7 @@ class TestTask extends Task { } /** - * Additonal info. + * Additional info. * * @return string */ diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/payment-gateway-suggestions/evaluate-suggestion.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/payment-gateway-suggestions/evaluate-suggestion.php index 89443db9cb7..25063d26ed3 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/payment-gateway-suggestions/evaluate-suggestion.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/features/payment-gateway-suggestions/evaluate-suggestion.php @@ -1,6 +1,6 @@ **_Note: It’s important to note to be very careful when adding load to a scenario. By accident a dangerous amount of load could be ran aginst the test environment that could effectively be like a denial-of-service attack on the test environment. Also important to consider any other consequences of running large load such as triggering of emails._** +>**_Note: It’s important to note to be very careful when adding load to a scenario. By accident a dangerous amount of load could be ran against the test environment that could effectively be like a denial-of-service attack on the test environment. Also important to consider any other consequences of running large load such as triggering of emails._** To execute a test scenario (for example `tests/simple-all-requests.js`). @@ -202,7 +202,7 @@ Create a custom Grafana dashboard using [these instructions](https://k6.io/docs/ k6 tests rely on HTTP requests in order to test the backend. They can either be constructed from scratch, by using the k6 recorder, or by converting a HAR file. -The k6 recorder is a browser extension which captures http requests generated as you perform actions in a tab. It generates a test with all the HTTP requests from your actions which can then be modified to make it execuatable. +The k6 recorder is a browser extension which captures http requests generated as you perform actions in a tab. It generates a test with all the HTTP requests from your actions which can then be modified to make it executable. Alternatively any application which captures HTTP requests can be used to figure out the requests to include in a test such as the network section within browser developer tools. @@ -246,9 +246,9 @@ Groups are used to organize common logic in the test scripts and can help with t Checks are like asserts but they don’t stop the tests if they record a failure (for example in a load test with 1000s of iterations of a request this allows for an isolated flakey iteration to not stop test execution). -All requests have had checks for at least a `200` http status repsonse added and most also have an additional check for a string contained in the response body. +All requests have had checks for at least a `200` http status response added and most also have an additional check for a string contained in the response body. --- ## Other Resources -[k6 documention](https://k6.io/docs/) is a very useful resource for test creation and execution. +[k6 documentation](https://k6.io/docs/) is a very useful resource for test creation and execution. diff --git a/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-functions-test.php b/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-functions-test.php index eb194b0cc39..f90fe936abc 100644 --- a/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-functions-test.php +++ b/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-functions-test.php @@ -283,7 +283,7 @@ class WC_Admin_Functions_Test extends \WC_Unit_Test_Case { $product = wc_get_product( $product->get_id() ); - // Stocks should have been increased to orignal amount minus the partially refunded stock. + // Stocks should have been increased to original amount minus the partially refunded stock. $this->assertEquals( 95, $product->get_stock_quantity() ); } diff --git a/plugins/woocommerce/tests/php/includes/admin/helper/class-wc-helper-test.php b/plugins/woocommerce/tests/php/includes/admin/helper/class-wc-helper-test.php index 7d591a49e60..a59e088cca6 100644 --- a/plugins/woocommerce/tests/php/includes/admin/helper/class-wc-helper-test.php +++ b/plugins/woocommerce/tests/php/includes/admin/helper/class-wc-helper-test.php @@ -6,7 +6,7 @@ class WC_Helper_Test extends \WC_Unit_Test_Case { /** - * Test that woo plugins are loaded correctly even if incorrect cache is intially set. + * Test that woo plugins are loaded correctly even if incorrect cache is initially set. */ public function test_get_local_woo_plugins_without_woo_header_cache() { $woocommerce_key = 'sample-woo-plugin.php'; diff --git a/plugins/woocommerce/tests/php/includes/wc-stock-functions-tests.php b/plugins/woocommerce/tests/php/includes/wc-stock-functions-tests.php index 4e04dc29d29..615e5309350 100644 --- a/plugins/woocommerce/tests/php/includes/wc-stock-functions-tests.php +++ b/plugins/woocommerce/tests/php/includes/wc-stock-functions-tests.php @@ -121,7 +121,7 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case { } /** - * Test inventory count after order status transtions which restores stock to another status which reduces stock. + * Test inventory count after order status transitions which restores stock to another status which reduces stock. * Stock should not have reduced, but will reduce after transition. */ public function test_status_transition_stock_restore_to_stock_reduce() { diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/ProductReviews/ReviewsListTableTest.php b/plugins/woocommerce/tests/php/src/Internal/Admin/ProductReviews/ReviewsListTableTest.php index 264763239f8..0077d5bad8a 100644 --- a/plugins/woocommerce/tests/php/src/Internal/Admin/ProductReviews/ReviewsListTableTest.php +++ b/plugins/woocommerce/tests/php/src/Internal/Admin/ProductReviews/ReviewsListTableTest.php @@ -89,7 +89,7 @@ class ReviewsListTableTest extends WC_Unit_Test_Case { * * @param string $review_status The review status. * @param string $column_name The current column name being output. - * @param string $primary_column The primary colum name. + * @param string $primary_column The primary column name. * @param bool $user_can_edit Whether the current user can edit reviews. * * @return void diff --git a/plugins/woocommerce/tests/php/src/Proxies/MockableLegacyProxyTest.php b/plugins/woocommerce/tests/php/src/Proxies/MockableLegacyProxyTest.php index 39d033365f7..ec0f2761a68 100644 --- a/plugins/woocommerce/tests/php/src/Proxies/MockableLegacyProxyTest.php +++ b/plugins/woocommerce/tests/php/src/Proxies/MockableLegacyProxyTest.php @@ -204,7 +204,7 @@ class MockableLegacyProxyTest extends \WC_Unit_Test_Case { } /** - * @testdox 'get_global' can be used to reigster replacements for globals. + * @testdox 'get_global' can be used to register replacements for globals. */ public function test_register_global_mocks_can_be_used_to_replace_globals() { $replacement_wpdb = new \stdClass(); diff --git a/tools/changelogger/class-package-formatter.php b/tools/changelogger/class-package-formatter.php index 03add787d82..5dd231f58de 100644 --- a/tools/changelogger/class-package-formatter.php +++ b/tools/changelogger/class-package-formatter.php @@ -48,7 +48,7 @@ class Package_Formatter extends Formatter implements FormatterPlugin { * @return string Link to the version's release. */ public function getReleaseLink( $version ) { - // Catpure anything past /woocommerce in the current working directory. + // Capture anything past /woocommerce in the current working directory. preg_match( '/\/packages\/js\/(.+)/', getcwd(), $path ); if ( ! count( $path ) ) { diff --git a/tools/monorepo-merge/src/commands/transfer-issues/index.ts b/tools/monorepo-merge/src/commands/transfer-issues/index.ts index 429789f0d0f..cee53398be1 100644 --- a/tools/monorepo-merge/src/commands/transfer-issues/index.ts +++ b/tools/monorepo-merge/src/commands/transfer-issues/index.ts @@ -76,7 +76,7 @@ export default class TransferIssues extends Command { if ( numberOfIssues === 0 ) { this.log( - 'There are no issues to trasnfer that match this query!' + 'There are no issues to transfer that match this query!' ); this.exit( 0 ); } diff --git a/tools/monorepo/check-changelogger-use.php b/tools/monorepo/check-changelogger-use.php index b9f07dd3c83..e298c8f91c0 100644 --- a/tools/monorepo/check-changelogger-use.php +++ b/tools/monorepo/check-changelogger-use.php @@ -216,7 +216,7 @@ foreach ( $touched_projects as $slug => $files ) { } elseif ( getenv( 'CI' ) ) { printf( "---\n" ); // Bracket message containing newlines for better visibility in GH's logs. printf( - "::error::Project %s is being changed, but no change file in %s is touched!%%0A%%0AUse `pnpm --filter=%s run changelog add` to add a change file.\n", + "::error::Project %s is being changed, but no change file in %s is touched!%%0A%%0AUse `pnpm --filter=./%s run changelog add` to add a change file.\n", $slug, "$slug/{$changelogger_projects[ $slug ]['changes-dir']}/", $slug diff --git a/tools/storybook/wordpress/css/about-rtl.css b/tools/storybook/wordpress/css/about-rtl.css index 2c5efe2afbd..39b9b1f92e6 100644 --- a/tools/storybook/wordpress/css/about-rtl.css +++ b/tools/storybook/wordpress/css/about-rtl.css @@ -287,7 +287,7 @@ grid-column-start: 4; } -/* Any columns following a section header need to be expicitly put into the second row, for IE support. */ +/* Any columns following a section header need to be explicitly put into the second row, for IE support. */ .about__section.has-2-columns .is-section-header ~ .column, .about__section.has-3-columns .is-section-header ~ .column, .about__section.has-4-columns .is-section-header ~ .column, diff --git a/tools/storybook/wordpress/css/about.css b/tools/storybook/wordpress/css/about.css index eff4e2940cb..25cd1eb0ecc 100644 --- a/tools/storybook/wordpress/css/about.css +++ b/tools/storybook/wordpress/css/about.css @@ -286,7 +286,7 @@ grid-column-start: 4; } -/* Any columns following a section header need to be expicitly put into the second row, for IE support. */ +/* Any columns following a section header need to be explicitly put into the second row, for IE support. */ .about__section.has-2-columns .is-section-header ~ .column, .about__section.has-3-columns .is-section-header ~ .column, .about__section.has-4-columns .is-section-header ~ .column, diff --git a/tools/storybook/wordpress/css/list-tables-rtl.css b/tools/storybook/wordpress/css/list-tables-rtl.css index 47375209462..3a0285f70e8 100644 --- a/tools/storybook/wordpress/css/list-tables-rtl.css +++ b/tools/storybook/wordpress/css/list-tables-rtl.css @@ -872,7 +872,7 @@ tr:hover .row-actions, tr.inline-edit-row td { padding: 0; - /* Prevents the focus style on .inline-edit-wrapper from being cutted-off */ + /* Prevents the focus style on .inline-edit-wrapper from being cut-off */ position: relative; } diff --git a/tools/storybook/wordpress/css/list-tables.css b/tools/storybook/wordpress/css/list-tables.css index 84e390d83eb..c95f5a283fe 100644 --- a/tools/storybook/wordpress/css/list-tables.css +++ b/tools/storybook/wordpress/css/list-tables.css @@ -871,7 +871,7 @@ tr:hover .row-actions, tr.inline-edit-row td { padding: 0; - /* Prevents the focus style on .inline-edit-wrapper from being cutted-off */ + /* Prevents the focus style on .inline-edit-wrapper from being cut-off */ position: relative; }